feat(codex): self-contained codex app-server transport (executor + provider + sign-in) (#11205)

Merged after conflict resolution: the 5 conflicting test files were the base-red drains that #11201 already landed on the tip — kept the tip versions; the feature content is untouched. Validated on the combined batch board + this branch: codex-app-server + codex-gpt56-catalog 25/25, typecheck:core clean, docs-counts green (351 providers), provider-consistency 268/351/0. The opt-in codex-app-server transport (JSON-RPC-over-WS, turn/completed-awaited close, Responses SSE bridge) leaves the default codex path untouched. Thank you @arminanton — a 3.4k-line transport with the docs wave and tests to match!
This commit is contained in:
Armin Anton” ∴
2026-08-23 06:20:06 -07:00
committed by GitHub
parent 968fa96105
commit 8f390efffd
153 changed files with 3358 additions and 182 deletions

View File

@@ -1303,6 +1303,30 @@ CURSOR_USER_AGENT="Cursor/3.4"
# set to true/1/yes to enable. Used by: open-sse/executors/codex.ts.
# OMNIROUTE_CODEX_DROP_NONSTANDARD_EVENTS=true
# Codex app-server WebSocket transport (opt-in). When a WebSocket URL and a
# capability token are both provided, Codex requests are routed through a local
# `codex app-server` sidecar over JSON-RPC instead of the HTTP Responses API.
# Each var is also settable per-connection via providerSpecificData; the env var
# is the process-wide fallback. Used by:
# open-sse/executors/codex/appServerConfig.ts.
#
# WebSocket endpoint of the codex app-server (ws:// or wss://). Required to
# enable the transport; leaving it unset keeps Codex on its HTTP transports.
# OMNIROUTE_CODEX_APPSERVER_WS=ws://127.0.0.1:8081
# Inline capability/bearer token presented to the app-server.
# OMNIROUTE_CODEX_APPSERVER_WS_TOKEN=deadbeef...
# Path to a file holding the capability token (produced by
# `codex app-server --ws-token-file <path>`). Used when the inline token above
# is not set.
# OMNIROUTE_CODEX_APPSERVER_WS_TOKEN_FILE=/run/codex-ws-token
# Working directory the app-server turn runs in (defaults to /tmp).
# OMNIROUTE_CODEX_APPSERVER_CWD=/tmp
# Approval policy passed to the app-server turn (e.g. never, on-request).
# OMNIROUTE_CODEX_APPSERVER_APPROVAL=never
# Sandbox policy passed to the app-server turn (e.g. read-only,
# workspace-write, danger-full-access).
# OMNIROUTE_CODEX_APPSERVER_SANDBOX=read-only
# ═══════════════════════════════════════════════════════════════════════════════
# 13. CLI FINGERPRINT COMPATIBILITY (Anti-Detection)
# ═══════════════════════════════════════════════════════════════════════════════

View File

@@ -92,5 +92,9 @@
# - x-api-key PUBLICO do Firefly web (documentado em open-sse/utils/publicCreds.ts:207);
# as duas ocorrencias sinalizadas estao em COMENTARIOS JSDoc, o runtime le de resolvePublicCred().
'''omniroute-kimi-sponsor-banner-dismissed-v\d+''',
# CheaperInference sponsor banner localStorage key (upstream #11196 /
# eb5797370). Same UI-identifier pattern as the kimi banner above, not a
# credential; the generic-api-key rule flags the long hyphenated string.
'''omniroute-cheaperinference-sponsor-banner-dismissed-v\d+''',
'''SunbreakWebUI1''',
]

View File

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

View File

@@ -7,7 +7,7 @@
# 🚀 OmniRoute — The Free AI Gateway
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 349 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 350 AI providers · 90+ 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 → 351 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 351 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
</div>
@@ -210,7 +210,7 @@ curl http://localhost:20128/v1/chat/completions \
</div>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint. 349 providers. Never stop building — OmniRoute picks the cheapest one that works. Six pillars: Never hit limits (auto-fallback across 349 providers in milliseconds, zero downtime) · Save up to 95% tokens (RTK + Caveman stacked compression cuts 1595%, ~89% avg on tool-heavy sessions) · $0 to start (90+ free tiers, 56 free forever — no card needed) · Every tool works (33 coding agents through one config) · One endpoint (OpenAI ↔ Claude ↔ Gemini ↔ Responses API at /v1) · Production-grade (circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals — 25,000+ tests)."/>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint. 351 providers. Never stop building — OmniRoute picks the cheapest one that works. Six pillars: Never hit limits (auto-fallback across 351 providers in milliseconds, zero downtime) · Save up to 95% tokens (RTK + Caveman stacked compression cuts 1595%, ~89% avg on tool-heavy sessions) · $0 to start (90+ free tiers, 56 free forever — no card needed) · Every tool works (33 coding agents through one config) · One endpoint (OpenAI ↔ Claude ↔ Gemini ↔ Responses API at /v1) · Production-grade (circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals — 25,000+ tests)."/>
<br/>
<br/>
@@ -461,7 +461,7 @@ All **19** strategies — mix & match per combo step:
</div>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — comparison table vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 349 providers, 90+ free providers 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, 43 i18n UI locales, 100% MIT self-hosted. OmniRoute is the only one with the full set; competitors show a mix of checks, partials and crosses. Verified from each project&apos;s docs."/>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — comparison table vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 351 providers, 90+ free providers 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, 43 i18n UI locales, 100% MIT self-hosted. OmniRoute is the only one with the full set; competitors show a mix of checks, partials and crosses. Verified from each project&apos;s docs."/>
<sub>📊 Full methodology &amp; per-feature detail vs 9router, OpenRouter, CLIProxyAPI &amp; LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
@@ -646,7 +646,7 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
</div>
> The most complete catalog of any open-source router: **349 providers**, **90+ with a free tier**, **56 free forever**.
> The most complete catalog of any open-source router: **351 providers**, **90+ with a free tier**, **56 free forever**.
<div align="center">

View File

@@ -43,8 +43,21 @@ x-common: &common
- LIVE_WS_ALLOWED_ORIGINS=${LIVE_WS_ALLOWED_ORIGINS:-http://localhost:20128,http://127.0.0.1:20128}
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
- NODE_OPTIONS=--max-old-space-size=2048
# Codex App-Server transport (provider: codex-app-server). Inert unless the
# `codex-app-server` compose profile is up (the sidecar below). Points the app
# at the internal sidecar; the capability token is shared via the mounted file.
- OMNIROUTE_CODEX_APPSERVER_WS=${OMNIROUTE_CODEX_APPSERVER_WS:-ws://codex-app-server:1456}
- OMNIROUTE_CODEX_APPSERVER_WS_TOKEN_FILE=${OMNIROUTE_CODEX_APPSERVER_WS_TOKEN_FILE:-/run/codex-appserver/token}
volumes:
- ./data:/app/data
# Shared capability token + codex auth for the app-server WS. Only meaningful
# when the codex-app-server profile is active. The token dir carries the WS
# capability token; the codex home is where the dashboard "Apply auth" writes
# ~/.codex/auth.json (getCliConfigPaths("codex") = <home>/.codex; the base
# image runs as `node`, so /home/node/.codex) and the SAME volume is mounted
# into the sidecar so its `codex app-server` reads the same auth.
- codex-appserver-token:/run/codex-appserver
- codex-appserver-home:/home/node/.codex
healthcheck:
test: ["CMD", "node", "healthcheck.mjs"]
interval: 30s
@@ -290,6 +303,59 @@ services:
profiles:
- cliproxyapi
# ── Profile: codex-app-server (Codex CLI app-server sidecar) ──────────
# A PLAIN Codex app-server for the `codex-app-server` provider: OmniRoute drives
# the Codex CLI's own `codex app-server` over JSON-RPC/WebSocket instead of
# replaying a session token to the API. It listens ONLY on the internal compose
# network (ws://codex-app-server:1456), guarded by a capability token — it is
# NEVER published to the host / internet. The Codex CLI (baked into
# omniroute:base) self-manages its OpenAI OAuth via the shared ~/.codex volume,
# which the dashboard "Apply auth" (device-OAuth) writes and this sidecar reads.
#
# NOTE: this is the GENERIC public sidecar. An operator wanting residential /
# UDP egress (via a TUN sidecar) runs that separately as an override; it is
# intentionally not shipped here.
codex-app-server:
image: omniroute:base
container_name: omniroute-codex-app-server
restart: unless-stopped
# Generate the WS capability token on first boot if absent, then run the
# app-server. entrypoint is overridden because the base image's default is the
# Next.js server.
entrypoint: ["/bin/sh", "-c"]
command:
- |
set -e
TOKEN_FILE=/run/codex-appserver/token
mkdir -p /run/codex-appserver
if [ ! -s "$$TOKEN_FILE" ]; then
# 32-byte hex capability token; shared with the app via the token volume.
TF="$$TOKEN_FILE" node -e 'require("fs").writeFileSync(process.env.TF, require("crypto").randomBytes(32).toString("hex"))' 2>/dev/null || \
{ head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' > "$$TOKEN_FILE"; }
chmod 600 "$$TOKEN_FILE"
fi
exec codex app-server \
--listen ws://0.0.0.0:1456 \
--ws-auth capability-token \
--ws-token-file "$$TOKEN_FILE"
environment:
- CODEX_HOME=/home/node/.codex
- RUST_LOG=${CODEX_APPSERVER_RUST_LOG:-warn}
volumes:
- codex-appserver-token:/run/codex-appserver
- codex-appserver-home:/home/node/.codex
# No `ports:` — internal-only. Reached at ws://codex-app-server:1456 over the
# compose network by the omniroute app.
healthcheck:
test:
["CMD", "node", "-e", "require('http').get('http://127.0.0.1:1456/readyz',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
profiles:
- codex-app-server
volumes:
chatgpt-web-codex-browser-data:
name: omniroute-chatgpt-web-codex-browser-data
@@ -301,3 +367,7 @@ volumes:
name: omniroute-qdrant-data
bifrost-data:
name: omniroute-bifrost-data
codex-appserver-token:
name: omniroute-codex-appserver-token
codex-appserver-home:
name: omniroute-codex-appserver-home

View File

@@ -17,7 +17,7 @@ It provides a single OpenAI-compatible endpoint (`/v1/*`) and routes traffic acr
Core capabilities:
- OpenAI-compatible API surface for CLI/tools (338 providers, 100 executors)
- OpenAI-compatible API surface for CLI/tools (349 providers, 107 executors)
- Request/response translation across provider formats
- Model combo fallback (multi-model sequence)
- Structured combo steps (`provider + model + connection`) with runtime ordering by `compositeTiers`

View File

@@ -451,7 +451,7 @@ open-sse/
├── types.d.ts
├── config/ Provider registries, header profiles, identity, …
├── handlers/ Request handlers (chat, embeddings, audio, image, …)
├── executors/ 101 provider-specific HTTP executors
├── executors/ 107 provider-specific HTTP executors
├── translator/ Format conversion (OpenAI ↔ Claude ↔ Gemini ↔ Cursor ↔ Kiro)
├── transformer/ Responses API ↔ Chat Completions stream transformer
├── services/ 80+ service modules (combos, fallback, quotas, identity, …)

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (350 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 the 80+ command surface: 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 (351 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 the 80+ command surface: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
<desc>Compact animated terminal cycling three real OmniRoute CLI commands with a typewriter effect and a scrolling subcommand ticker; the first frame shows the completed providers-list screen.</desc>
<defs><clipPath id="tickerClip"><rect x="12" y="304" width="1176" height="40"/></clipPath><clipPath id="tw0"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;31;61;92;122;153;184;214;245;245" keyTimes="0;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw1"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;26;51;76;102;128;153;178;204;204" keyTimes="0;0.348;0.351;0.357;0.363;0.369;0.375;0.381;0.387;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw2"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;20;41;61;82;102;122;143;163;163" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/></rect></clipPath></defs>
<rect width="1200" height="350" fill="#0d1117"/>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

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

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint, 350 providers — never stop building, OmniRoute picks the cheapest one that works. Six pillars. Never hit limits: auto-fallback across 350 providers in milliseconds, quota out means the next provider takes over with zero downtime. Save up to 95 percent of tokens: RTK plus Caveman stacked compression cuts 15 to 95 percent of eligible tokens, about 89 percent average on tool-heavy sessions. Zero dollars to start: 90+ providers with a free tier, 56 free forever — Qoder, Pollinations, Cloudflare, SiliconFlow — no card needed. Every tool works: 33 coding agents including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation — point any tool at /v1 and it just works. Production-grade: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals — 25,000+ tests.">
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint, 351 providers — never stop building, OmniRoute picks the cheapest one that works. Six pillars. Never hit limits: auto-fallback across 351 providers in milliseconds, quota out means the next provider takes over with zero downtime. Save up to 95 percent of tokens: RTK plus Caveman stacked compression cuts 15 to 95 percent of eligible tokens, about 89 percent average on tool-heavy sessions. Zero dollars to start: 90+ providers with a free tier, 56 free forever — Qoder, Pollinations, Cloudflare, SiliconFlow — no card needed. Every tool works: 33 coding agents including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation — point any tool at /v1 and it just works. Production-grade: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals — 25,000+ tests.">
<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">350 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">351 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 350 providers in</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 351 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 — zero downtime.</text>
</g>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 350 providers — 90+ 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: 350 AI providers, 90+ 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 351 providers — 90+ 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: 351 AI providers, 90+ 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">350 providers</tspan><tspan fill="#7ee787" font-weight="800">90+ 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">351 providers</tspan><tspan fill="#7ee787" font-weight="800">90+ free</tspan> — through one endpoint.</text>
<!-- plug line -->
<text x="48" y="222" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16.5" fill="#a1a1aa">Claude Code · Codex · Cursor · Cline · Copilot · Antigravity&#160;&#160;&#160;&#160;<tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,89 @@
---
title: "OpenAI Codex (App-Server) provider"
version: 3.8.50
lastUpdated: 2026-08-22
---
# OpenAI Codex — App-Server provider (`codex-app-server`)
OmniRoute exposes **two** ways to use OpenAI Codex:
| Provider | How it talks to OpenAI | Usage caveat |
|---|---|---|
| **`codex`** | Replays your ChatGPT/OpenAI OAuth token directly to the Responses API | **Yes** — the official session is not authorized for proxy/router use |
| **`codex-app-server`** | Drives the **Codex CLI's own `codex app-server`** over JSON-RPC/WebSocket; the CLI owns and self-refreshes its OAuth (`~/.codex/auth.json`) exactly like an interactive `codex` session | **No** — OmniRoute never replays a token to the API |
Because `codex-app-server` never replays a token, it does not carry the
session-replay usage caveat. It does require a **Codex CLI reachable at the
configured app-server URL**, and that CLI must be **signed in**.
---
## 1. Architecture
```
┌─ OmniRoute app ─────────────────┐ ┌─ codex-app-server sidecar ─────────┐
│ CodexAppServerExecutor │ WS │ codex app-server │
│ ws://codex-app-server:1456 ─────┼───────▶│ --listen ws://0.0.0.0:1456 │
│ (+ capability token) │ JSON │ --ws-auth capability-token │
│ │ RPC │ self-manages OpenAI OAuth │
└──────────────────────────────────┘ │ (~/.codex/auth.json, auto-refresh) │
│ shares (compose volumes) └─────────────────────────────────────┘
codex-appserver-token → the WS capability token (both mount it)
codex-appserver-home → ~/.codex (auth.json written by the dashboard,
read by the sidecar's codex app-server)
```
- The sidecar listens **only** on the internal compose network
(`ws://codex-app-server:1456`) behind a capability token. It is **never**
published to the host or internet.
- The Codex CLI is baked into `omniroute:base`, so no codex install is needed on
the host or the user's machine when you run the sidecar.
## 2. Bring it up
```bash
# Start the stack WITH the codex app-server sidecar profile:
docker compose --profile base --profile codex-app-server up -d
# (podman: podman compose --profile base --profile codex-app-server up -d)
```
The sidecar mints its WS capability token on first boot (into the shared
`codex-appserver-token` volume) and the app reads the same token via
`OMNIROUTE_CODEX_APPSERVER_WS_TOKEN_FILE`. No manual token wiring needed.
## 3. Connect + sign in
1. In the dashboard, add a connection for **OpenAI Codex (App-Server)**. No API
key or token is required (it's a no-auth provider — the sidecar owns auth).
2. If the sidecar's Codex CLI is **not yet signed in**, the connection health
check reports *"running but not signed in"* (not a red auth error). Use
**Sign in with ChatGPT**: this runs the standard Codex device-OAuth in your
browser and then writes `~/.codex/auth.json` into the shared volume via
**Apply auth** (the same one login serves both the `codex` and
`codex-app-server` providers).
3. Once signed in, the health check goes green (it verifies both `/readyz` **and**
`account/read` — i.e. up *and* authenticated) and turns work.
The dashboard never clobbers a healthy existing `~/.codex/auth.json` — it writes
only when the file is absent or its token is stale (a backup is always taken).
## 4. Deployment scenarios
- **Operator with an already-authenticated Codex CLI** — mount your host
`~/.codex` into the sidecar (`codex-appserver-home`) and skip the sign-in step.
- **Public user, no codex installed locally** — irrelevant: the sidecar has the
CLI. The user only authenticates through the dashboard.
- **Bare-metal OmniRoute (no sidecar, host codex)** — point
`OMNIROUTE_CODEX_APPSERVER_WS` at your own `codex app-server` and ensure the
host codex is signed in; the "codex not installed" hint appears if the binary
is missing.
## 5. Residential / UDP egress (operator extra, not shipped)
The generic sidecar above egresses over the container's normal network. An
operator who needs Codex traffic to egress via a **residential exit** (e.g. a TUN
tailscale sidecar carrying TCP + UDP/QUIC) runs that as a separate compose
override; it is intentionally **not** part of the shipped `codex-app-server`
profile. See the internal operations runbook for that setup.

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -737,6 +737,12 @@ REQUEST_TIMEOUT_MS (global override)
| `OMNIROUTE_AGENT_GOAL_READINESS_MAX_TIMEOUT_MS` | `600000` | Maximum first-event readiness window for detected `/goal` agent runs or requests forced with `x-omniroute-agent-goal`. |
| `OMNIROUTE_AGENT_GOAL_STREAM_RECOVERY` | `true` | Enable early stream recovery automatically for detected `/goal` agent runs. Set `false`/`0`/`off` to disable the goal-specific opt-in. This can only ADD recovery on top of the operator default — it never overrides an explicit `STREAM_RECOVERY_ENABLED`/DB settings opt-out. |
| `OMNIROUTE_CODEX_DROP_NONSTANDARD_EVENTS` | `true` | Strip non-standard `codex.*` SSE events (e.g. `codex.rate_limits`) that break the OpenAI SDK's `responses.stream()` with a 502. Default ON (#11014). Set `0`/`false`/`no`/`off` to forward them. |
| `OMNIROUTE_CODEX_APPSERVER_WS` | _(unset)_ | Opt-in Codex app-server transport. WebSocket endpoint (`ws://`/`wss://`) of a local `codex app-server` sidecar. When set together with a token, Codex requests are routed over JSON-RPC to the sidecar instead of the HTTP Responses API. Also settable per-connection via `providerSpecificData.codexAppServerUrl`. Used by `open-sse/executors/codex/appServerConfig.ts`. |
| `OMNIROUTE_CODEX_APPSERVER_WS_TOKEN` | _(unset)_ | Inline capability/bearer token presented to the app-server. Per-connection override: `providerSpecificData.codexAppServerToken`. |
| `OMNIROUTE_CODEX_APPSERVER_WS_TOKEN_FILE` | _(unset)_ | Path to a file holding the app-server capability token (from `codex app-server --ws-token-file`). Used when `OMNIROUTE_CODEX_APPSERVER_WS_TOKEN` is unset. Per-connection override: `providerSpecificData.codexAppServerTokenFile`. |
| `OMNIROUTE_CODEX_APPSERVER_CWD` | `/tmp` | Working directory the app-server turn runs in. Per-connection override: `providerSpecificData.codexAppServerCwd`. |
| `OMNIROUTE_CODEX_APPSERVER_APPROVAL` | _(unset)_ | Approval policy passed to the app-server turn (e.g. `never`, `on-request`). Per-connection override: `providerSpecificData.codexAppServerApprovalPolicy`. |
| `OMNIROUTE_CODEX_APPSERVER_SANDBOX` | _(unset)_ | Sandbox policy passed to the app-server turn (e.g. `read-only`, `workspace-write`, `danger-full-access`). Per-connection override: `providerSpecificData.codexAppServerSandbox`. |
| `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. |
| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive the full response body. |

View File

@@ -10,7 +10,7 @@ lastUpdated: 2026-08-23
> Regenerate with: `npm run gen:provider-reference`
> **Last generated:** 2026-08-23
Total providers: **350**. See category breakdown below.
Total providers: **351**. See category breakdown below.
## Categories
@@ -34,7 +34,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
---
## No-auth Providers (no key required) (12)
## No-auth Providers (no key required) (13)
| ID | Alias | Name | Tags | Website | Notes | Tool calling |
|----|-------|------|------|---------|-------|--------------|
@@ -42,6 +42,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `auggie` | `aug` | Augment (Auggie CLI) | No-auth | [link](https://augmentcode.com) | No API key stored by OmniRoute. Install the Auggie CLI and run `auggie login` on this machine, then OmniRoute spawns it locally for each request. | — |
| `chipotle` | `pepper` | Chipotle Pepper AI (Free) | No-auth | [link](https://amelia.chipotle.com) | No credentials required. Uses Chipotle's public support chatbot via reverse-engineered SockJS/STOMP protocol. | — |
| `cloudflare-playground` | `cfp` | Cloudflare AI Playground | No-auth | [link](https://playground.ai.cloudflare.com) | No credentials required — anonymous browser sessions over a reverse-engineered cf_agent WebSocket protocol (Playwright transport). | — |
| `codex-app-server` | `cxa` | OpenAI Codex (App-Server) | No-auth | [link](https://developers.openai.com/codex/cli) | No token stored by OmniRoute. The Codex CLI app-server manages its own ChatGPT sign-in (~/.codex/auth.json, auto-refreshed). Use “Sign in with ChatGPT” if the CLI is not yet authenticated. | — |
| `devin-cli-agentic` | `dva` | Devin CLI Agentic Bridge | No-auth | [link](https://docs.devin.ai/work-with-devin/devin-cli) | Authentication is owned by the official Devin CLI in its isolated bridge volume. | emulated |
| `duckduckgo-web` | `ddgw` | DuckDuckGo AI Chat | No-auth | [link](https://duckduckgo.com/duckchat) | No credentials required — DuckDuckGo AI Chat is anonymous and free. | emulated |
| `felo-web` | `felo` | Felo | No-auth | [link](https://felo.ai) | No credentials required — Felo is a free, no-signup chat/search aggregator. | — |
@@ -438,7 +439,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
- Catalog: [`src/shared/constants/providers.ts`](../../src/shared/constants/providers.ts)
- Registry (per-model details): [`open-sse/config/providerRegistry.ts`](../../open-sse/config/providerRegistry.ts)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (107 implementations)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (108 implementations)
- Translators: [`open-sse/translator/`](../../open-sse/translator/)
## See Also

View File

@@ -1,6 +1,6 @@
# OmniRoute
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in 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 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **350 AI providers** with automatic format translation
- **351 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **18 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, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

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

View File

@@ -0,0 +1,36 @@
import type { RegistryEntry } from "../../shared.ts";
import { codexProvider } from "../codex/index.ts";
/**
* OpenAI Codex — App-Server transport (sibling of the `codex` provider).
*
* This provider drives the Codex CLI's own `codex app-server` over JSON-RPC/
* WebSocket (executor: "codex-app-server"). Unlike the `codex` provider — which
* replays the user's ChatGPT/OpenAI OAuth token directly to the Responses API —
* the app-server process OWNS and self-refreshes its OpenAI auth
* (~/.codex/auth.json), exactly like an interactive `codex` session. OmniRoute
* never receives or replays a token, so there is no `authType: "oauth"` and no
* usage-caveat: `authType: "none"`.
*
* The connection target (ws:// URL + capability token) is supplied per-connection
* via providerSpecificData (codexAppServerUrl / codexAppServerToken[File]) and
* resolved by resolveAppServerConfig — NOT from `baseUrl` below, which is a
* documentation sentinel only.
*
* Models are shared with the `codex` provider (same underlying ChatGPT Codex
* backend), imported from codexProvider so the two stay in lockstep.
*/
export const codexAppServerProvider: RegistryEntry = {
id: "codex-app-server",
alias: "cxa",
format: "openai-responses",
executor: "codex-app-server",
// Sentinel: the executor dials the WebSocket app-server URL from
// providerSpecificData, not this baseUrl. Kept for catalog/debug display.
baseUrl: "codex-app-server://cli/websocket",
reasoningTransport: "opaque",
authType: "none",
authHeader: "none",
defaultContextLength: 400000,
models: [...codexProvider.models],
};

View File

@@ -0,0 +1,448 @@
import {
bridgeToResponsesSSE,
buildResponseJSON,
} from "../vendor/codex-chatgpt-web/bridge.ts";
import { AsyncEventQueue } from "../vendor/codex-chatgpt-web/event-queue.ts";
import type { AdapterEvent } from "../vendor/codex-chatgpt-web/types.ts";
import { sanitizeErrorMessage } from "../utils/error.ts";
import { PROVIDERS } from "../config/constants.ts";
import { BaseExecutor, type ExecuteInput, type ExecutorExecuteResult } from "./base.ts";
import {
CodexAppServerClient,
type CodexAppServerClientOptions,
} from "./codex/appServerClient.ts";
import { resolveAppServerConfig, type CodexAppServerConfig } from "./codex/appServerConfig.ts";
import {
translateNotification,
translateToolCall,
type DynamicToolCallLike,
} from "./codex/appServerEvents.ts";
const JSON_HEADERS = { "Content-Type": "application/json" };
const SSE_HEADERS = {
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
"Content-Type": "text/event-stream; charset=utf-8",
};
/** A single text UserInput as accepted by turn/start (text_elements is required). */
interface CodexTextUserInput {
type: "text";
text: string;
text_elements: [];
}
/**
* Flatten an OpenAI Responses request body into the plain prompt text the
* app-server turn expects. The body's `input` is a string, a single message item,
* or an array of message items with `content` parts; we concatenate the user-facing
* text. This is intentionally lossless-enough for a text turn (images/tool parts are
* out of scope for the initial app-server transport).
*/
export function extractPromptText(body: unknown): string {
if (!body || typeof body !== "object") return "";
const input = (body as Record<string, unknown>).input;
if (typeof input === "string") return input;
if (input == null) return "";
const items = Array.isArray(input) ? input : [input];
const chunks: string[] = [];
for (const item of items) {
collectText(item, chunks);
}
return chunks.join("\n").trim();
}
function collectText(item: unknown, out: string[]): void {
if (typeof item === "string") {
if (item.length > 0) out.push(item);
return;
}
if (!item || typeof item !== "object") return;
const rec = item as Record<string, unknown>;
if (typeof rec.text === "string" && rec.text.length > 0) {
out.push(rec.text);
return;
}
const content = rec.content;
if (typeof content === "string") {
if (content.length > 0) out.push(content);
return;
}
if (Array.isArray(content)) {
for (const part of content) {
if (part && typeof part === "object") {
const text = (part as Record<string, unknown>).text;
if (typeof text === "string" && text.length > 0) out.push(text);
} else if (typeof part === "string" && part.length > 0) {
out.push(part);
}
}
}
}
/** Optional reasoning effort carried on the Responses body (`reasoning.effort`). */
function extractEffort(body: unknown): string | undefined {
if (!body || typeof body !== "object") return undefined;
const reasoning = (body as Record<string, unknown>).reasoning;
if (reasoning && typeof reasoning === "object") {
const effort = (reasoning as Record<string, unknown>).effort;
if (typeof effort === "string" && effort.length > 0) return effort;
}
return undefined;
}
/** A codex app-server DynamicToolSpec (experimental-api) advertised on thread/start. */
interface DynamicToolFunctionSpec {
type: "function";
name: string;
description: string;
inputSchema: Record<string, unknown>;
}
interface AppServerToolMaps {
/** wireName -> {namespace, name} for restoring MCP namespaced calls in the bridge. */
namespace: Map<string, { namespace: string; name: string }>;
/** wireNames the bridge must relay as custom_tool_call (freeform, e.g. apply_patch). */
freeform: Set<string>;
/** wireNames the bridge must relay as tool_search_call. */
toolSearch: Set<string>;
/** DynamicToolSpecs to advertise to codex on thread/start (experimental-api). */
specs: DynamicToolFunctionSpec[];
}
const EMPTY_OBJECT_SCHEMA: Record<string, unknown> = { type: "object", properties: {} };
const FREEFORM_INPUT_SCHEMA: Record<string, unknown> = {
type: "object",
properties: { input: { type: "string", description: "Raw tool input." } },
required: ["input"],
};
function asRecord(v: unknown): Record<string, unknown> | null {
return v && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>) : null;
}
/**
* Build the bridge tool maps + the codex dynamicTools specs from the harness's
* Responses `tools` array. This mirrors chatgpt-web-codex.ts:toolMaps() /
* parser.ts:buildTools(): every harness tool is exposed to codex FLAT under its
* wire name ("<namespace>__<name>" for MCP tools) so the round-trip is
* namespace-preserving (codex echoes the call via item/tool/call; the bridge
* restores {namespace, name} from `toolNsMap`). Custom (freeform) and tool_search
* tools are tracked so the bridge relays them as custom_tool_call / tool_search_call.
*/
function buildAppServerToolMaps(body: unknown): AppServerToolMaps {
const namespace = new Map<string, { namespace: string; name: string }>();
const freeform = new Set<string>();
const toolSearch = new Set<string>();
const specs: DynamicToolFunctionSpec[] = [];
const rec = asRecord(body);
const tools = rec && Array.isArray(rec.tools) ? (rec.tools as unknown[]) : [];
const pushFn = (name: string, description: string, inputSchema: Record<string, unknown>) => {
specs.push({ type: "function", name, description, inputSchema });
};
for (const raw of tools) {
const t = asRecord(raw);
if (!t) continue;
const type = t.type;
const desc = typeof t.description === "string" ? t.description : "";
if (type === "function" && typeof t.name === "string") {
const wireName = t.name;
pushFn(wireName, desc, asRecord(t.parameters) ?? EMPTY_OBJECT_SCHEMA);
} else if (type === "namespace" && Array.isArray(t.tools) && typeof t.name === "string") {
const ns = t.name;
for (const innerRaw of t.tools as unknown[]) {
const inner = asRecord(innerRaw);
if (inner && inner.type === "function" && typeof inner.name === "string") {
const wireName = `${ns}__${inner.name}`;
namespace.set(wireName, { namespace: ns, name: inner.name });
const innerDesc = typeof inner.description === "string" ? inner.description : "";
pushFn(wireName, innerDesc, asRecord(inner.parameters) ?? EMPTY_OBJECT_SCHEMA);
}
}
} else if (type === "custom" && typeof t.name === "string") {
const wireName = t.name;
freeform.add(wireName);
pushFn(wireName, desc, FREEFORM_INPUT_SCHEMA);
} else if (type === "tool_search") {
const wireName = "tool_search";
toolSearch.add(wireName);
pushFn(
wireName,
desc || "Search for additional tools to load for the next turn.",
asRecord(t.parameters) ?? {
type: "object",
properties: { query: { type: "string" }, limit: { type: "number" } },
required: ["query"],
}
);
} else if (
typeof t.name === "string" &&
type !== "web_search" &&
type !== "image_generation" &&
type !== "web_search_preview"
) {
// Any other named, client-executed tool → pass through as a function so the
// routed model can call it; the bridge relays its call as a function_call.
pushFn(t.name, desc, asRecord(t.parameters) ?? EMPTY_OBJECT_SCHEMA);
}
// web_search / image_generation are OpenAI-hosted server-side tools — not relayable.
}
return { namespace, freeform, toolSearch, specs };
}
/**
* Executor for the Codex app-server WS transport. Drives one turn against a local
* `codex app-server` over JSON-RPC and re-emits its notifications as OpenAI
* Responses SSE via the shared bridge.
*
* Errors are delivered IN-BAND (an `error` AdapterEvent → `response.failed` SSE
* frame for streaming, or an error field in the JSON body for non-streaming),
* never thrown out of execute().
*/
export class CodexAppServerExecutor extends BaseExecutor {
private readonly clientOptions: CodexAppServerClientOptions;
/**
* @param clientOptions transport options (websocketFn, timeouts).
* @param providerId which provider identity this executor reports as. Defaults
* to "codex" so the existing per-connection `codexTransport==="app-server"`
* flag path (routed through CodexExecutor for the `codex` provider) keeps its
* original identity. The first-class `codex-app-server` sibling passes
* "codex-app-server" so logs/quota scoping and the golden executor map reflect
* the real provider. Falls back to PROVIDERS.codex when the sibling registry
* entry is not present (defensive; both share the codex backend).
*/
constructor(clientOptions: CodexAppServerClientOptions = {}, providerId = "codex") {
super(providerId, PROVIDERS[providerId] ?? PROVIDERS.codex);
this.clientOptions = clientOptions;
}
override async execute(input: ExecuteInput): Promise<ExecutorExecuteResult> {
const psd = input.credentials?.providerSpecificData;
const config = resolveAppServerConfig(psd);
if (!config) {
return errorResponse(
503,
"Codex app-server transport is not configured (missing url or token)",
"codex_app_server_unconfigured"
);
}
const promptText = extractPromptText(input.body);
const effort = extractEffort(input.body);
const toolMaps = buildAppServerToolMaps(input.body);
const hasTools = toolMaps.specs.length > 0;
const events = new AsyncEventQueue<AdapterEvent>();
const client = new CodexAppServerClient(this.clientOptions);
const run = async () => {
let terminated = false;
// Resolves when the turn reaches a terminal state (turn/completed, error,
// or an item/tool/call passthrough). `turn/start` resolving only means the
// turn was ACCEPTED (status: inProgress) — the model's output arrives later
// as notifications. run() MUST await this before the finally-block closes
// the client, otherwise the socket is torn down mid-turn and the event
// queue never receives its terminal event (the request then hangs until the
// caller's timeout). See translateNotification: it returns true on the
// terminal notification, which is where we settle this.
let settleTurn!: () => void;
const turnDone = new Promise<void>((resolve) => {
settleTurn = resolve;
});
const markTerminated = () => {
if (terminated) return;
terminated = true;
settleTurn();
};
const finishTurn = () => {
if (terminated) return;
events.push({ type: "done", endTurn: true });
events.close();
markTerminated();
};
try {
await client.connect(config.url, config.token);
await client.request("initialize", {
clientInfo: {
name: "omniroute-codex-app-server",
title: null,
version: "1.0",
},
// Harness function tools are advertised via thread/start's `dynamicTools`,
// which is an EXPERIMENTAL app-server field: opt into experimental API so
// codex accepts it (and can emit the item/tool/call ServerRequest).
capabilities: hasTools
? { experimentalApi: true, requestAttestation: false }
: null,
});
const threadResult = (await client.request("thread/start", {
cwd: config.cwd,
// OmniRoute is a router: the HARNESS that consumes OmniRoute owns tool
// execution and policy. codex must therefore NEVER block a turn waiting
// on its own interactive approval, and its own sandbox must not gate the
// model — the harness decides what actually runs. So we pair
// approvalPolicy:"never" (non-interactive; codex never prompts) with
// sandbox:"danger-full-access" (codex's own sandbox imposes no
// restriction), mirroring codexInstructions.ts:50 ("never +
// danger-full-access = take advantage of it"). Any server→client
// approval request that still arrives is auto-APPROVED by the client
// (see CodexAppServerClient), never denied — denial would sabotage the
// harness's tool calls. Callers can override both via providerSpecificData.
approvalPolicy: config.approvalPolicy ?? "never",
sandbox: config.sandbox ?? "danger-full-access",
// INBOUND harness tools → codex. The client tells the app-server which
// function tools are available for the thread via the `dynamicTools`
// field on thread/start (a DynamicToolSpec[] under the experimental API,
// verified from the real codex binary; see appServerEvents.ts). codex
// then invokes them by sending the `item/tool/call` ServerRequest back
// to the client (DynamicToolCallParams), which we PASS THROUGH.
...(hasTools ? { dynamicTools: toolMaps.specs } : {}),
})) as { thread?: { id?: unknown }; threadId?: unknown };
// The live app-server (codex 0.149.0) returns the thread under
// result.thread.id — NOT a top-level threadId (verified against the real
// binary 2026-08-22). Keep the top-level fallback for forward/back compat.
const threadId =
threadResult && typeof threadResult.thread?.id === "string"
? threadResult.thread.id
: threadResult && typeof threadResult.threadId === "string"
? threadResult.threadId
: "";
client.onNotification((method, params) => {
if (terminated) return;
const isTerminal = translateNotification(method, params, (event) => events.push(event));
if (isTerminal) {
events.close();
markTerminated();
}
});
// OUTBOUND codex tool call → harness. codex asks us to execute a harness
// tool via the `item/tool/call` ServerRequest. OmniRoute is a STATELESS
// ROUTER and CANNOT execute the harness's tool (the tool body lives in the
// harness downstream). So we PASS IT THROUGH: emit tool_call_* AdapterEvents
// (the bridge renders a Responses function_call / custom_tool_call /
// tool_search_call), settle the app-server request with a benign
// DynamicToolCallResponse so codex does not hang, and COMPLETE the turn.
// The harness runs the tool and replays the result in a fresh /v1/responses
// request (the stateless-full-history contract every OmniRoute provider uses).
client.onToolCall((_id, params, api) => {
if (terminated) return;
const toolParams = (params && typeof params === "object" ? params : {}) as DynamicToolCallLike;
translateToolCall(toolParams, (event) => events.push(event));
// Settle the app-server request so the socket does not stall. The router
// does not have the tool output (the harness will produce it next turn),
// so we report the passthrough as an unsuccessful in-line result and end
// the turn — the function_call has already been surfaced to the harness.
api.respond({
contentItems: [
{
type: "inputText",
text: "router: tool executed by harness; call surfaced as function_call",
},
],
success: false,
});
finishTurn();
});
const onAbort = () => {
try {
client.notify("turn/interrupt", { threadId, turnId: "" });
} catch {
/* interrupt best-effort */
}
// Unblock run() so the finally-block can tear down the client. Without
// this, an aborted request would wait on turnDone until the terminal
// notification that will never come.
if (!terminated) {
events.close();
markTerminated();
}
};
input.signal?.addEventListener("abort", onAbort, { once: true });
const turnInput: CodexTextUserInput[] = [
{ type: "text", text: promptText, text_elements: [] },
];
await client.request("turn/start", {
threadId,
input: turnInput,
model: input.model,
...(effort ? { effort } : {}),
});
// `turn/start` resolving only ACCEPTS the turn (status: inProgress). The
// model's output (agentMessage deltas) and the terminal turn/completed
// arrive AFTER, as notifications. Wait for the terminal signal before
// falling through to the finally-block — otherwise client.close() tears
// down the socket mid-turn and the queue never closes (request hangs).
await turnDone;
} catch (err) {
if (!terminated) {
events.push({
type: "error",
message: sanitizeErrorMessage(err instanceof Error ? err.message : err),
status: 502,
errorType: "provider_error",
code: "codex_app_server_turn_failed",
});
events.close();
markTerminated();
}
} finally {
client.close();
}
};
if (!input.stream) {
const running = run();
const collected = await events.collect();
await running;
const response = buildResponseJSON(collected, input.model, {
toolNsMap: toolMaps.namespace,
freeformToolNames: toolMaps.freeform,
toolSearchToolNames: toolMaps.toolSearch,
});
return {
response: new Response(JSON.stringify(response), { status: 200, headers: JSON_HEADERS }),
url: config.url,
};
}
void run();
const stream = bridgeToResponsesSSE(
events,
input.model,
toolMaps.namespace,
toolMaps.freeform,
toolMaps.toolSearch,
() => client.close(),
2_000
);
return {
response: new Response(stream, { status: 200, headers: SSE_HEADERS }),
url: config.url,
};
}
}
function errorResponse(status: number, message: string, code: string): Response {
return new Response(
JSON.stringify({
error: {
code,
message: sanitizeErrorMessage(message),
type: status >= 500 ? "provider_error" : "invalid_request_error",
},
}),
{ status, headers: JSON_HEADERS }
);
}
// re-export config type for consumers/tests
export type { CodexAppServerConfig };

View File

@@ -58,6 +58,8 @@ import {
type CodexEffortLevel as EffortLevel,
} from "./codex/reasoningSuffix.ts";
import { repairMissingCodexToolCallOutputs } from "./codex/toolCallRepair.ts";
import { resolveAppServerConfig } from "./codex/appServerConfig.ts";
import { CodexAppServerExecutor } from "./codex-app-server.ts";
// Re-exported for external importers (tests + provider services).
export { isCodexFreePlan, normalizeCodexTools } from "./codex/tools.ts";
@@ -102,6 +104,12 @@ export function __setCodexWebSocketTransportForTesting(
_websocketOverride = websocket;
}
// Exposed for the app-server transport, which needs the same wreq-js websocket
// factory (with the testing override honored) to open its JSON-RPC socket.
export function getCodexAppServerWebsocketTransport(): WebsocketFn | null {
return getCodexWebSocketTransport();
}
function codexWebSocketUnavailableResponse(): Response {
return new Response(
JSON.stringify({
@@ -395,6 +403,34 @@ function isCodexWsGloballyEnabled(): boolean {
}
}
/**
* Global Codex app-server kill-switch (feature flag OMNIROUTE_CODEX_APP_SERVER_ENABLED,
* default ON). Fail-open, mirroring isCodexWsGloballyEnabled.
*/
function isCodexAppServerGloballyEnabled(): boolean {
try {
return isFeatureFlagEnabled("OMNIROUTE_CODEX_APP_SERVER_ENABLED");
} catch {
return true;
}
}
/**
* True when the connection opted into the app-server transport
* (providerSpecificData.codexTransport === "app-server") AND the app-server is
* configured (URL + token resolvable) AND the global flag is on. Selected BEFORE
* the websocket check so it wins when configured.
*/
export function isCodexAppServerRequired(credentials: unknown): boolean {
if (!isCodexAppServerGloballyEnabled()) return false;
const providerSpecificData =
credentials && typeof credentials === "object"
? (credentials as { providerSpecificData?: Record<string, unknown> }).providerSpecificData
: null;
if (providerSpecificData?.codexTransport !== "app-server") return false;
return !!resolveAppServerConfig(providerSpecificData);
}
export function isCodexResponsesWebSocketRequired(_model: string, credentials: unknown): boolean {
// Global kill-switch (default ON). When disabled, Codex never uses the WS
// transport — even per-connection codexTransport=websocket falls back to the
@@ -760,6 +796,8 @@ function normalizeCodexWsHeaders(headers: Record<string, string>): Record<string
* IMPORTANT: Includes chatgpt-account-id header for workspace binding.
*/
export class CodexExecutor extends BaseExecutor {
private appServer: CodexAppServerExecutor | null = null;
constructor() {
super("codex", PROVIDERS.codex);
}
@@ -778,6 +816,15 @@ export class CodexExecutor extends BaseExecutor {
);
const nextInput = { ...requestInput, credentials };
if (isCodexAppServerRequired(nextInput.credentials)) {
if (!this.appServer) {
this.appServer = new CodexAppServerExecutor({
websocketFn: getCodexAppServerWebsocketTransport(),
});
}
return this.appServer.execute(nextInput);
}
if (!isCodexResponsesWebSocketRequired(nextInput.model, nextInput.credentials)) {
const httpResult = await super.execute(nextInput);
if (codexDropNonstandardEvents()) {

View File

@@ -0,0 +1,102 @@
/**
* Layer-2 auth-status probe for the Codex app-server transport.
*
* The HTTP `/readyz` endpoint only proves the app-server PROCESS is up — not that
* its Codex CLI is signed in. A public user whose CLI is not yet authenticated
* would otherwise see a green "ready" badge and then fail on the first turn with
* an upstream auth error. This probe opens the same JSON-RPC/WebSocket the
* executor uses and calls `account/read` (verified against codex 0.149.0): an
* authenticated server returns `{ account: { type, email, planType }, ... }`;
* a logged-out server returns no account (or an error). So the presence of
* `result.account` is the "authenticated" signal.
*
* Kept separate from the executor turn path so the health check pulls in only the
* lightweight client + transport, and so it is independently unit-testable with a
* fake websocketFn.
*/
import {
CodexAppServerClient,
type CodexAppServerWebsocketFn,
} from "./appServerClient.ts";
import type { CodexAppServerConfig } from "./appServerConfig.ts";
export type CodexAppServerAuthStatus =
| { state: "authenticated"; account: { type?: string; email?: string; planType?: string } }
| { state: "logged_out"; reason: string }
| { state: "unknown"; reason: string };
interface AccountReadResult {
account?: { type?: unknown; email?: unknown; planType?: unknown } | null;
requiresOpenaiAuth?: unknown;
}
function str(v: unknown): string | undefined {
return typeof v === "string" && v.length > 0 ? v : undefined;
}
/**
* Open a short-lived WS to the app-server, initialize, and read the account.
* Returns an auth status; never throws (maps failures to state "unknown").
*
* @param config resolved app-server config (url + capability token).
* @param websocketFn the wreq-js websocket factory
* (getCodexAppServerWebsocketTransport()); when null, returns "unknown".
* @param timeoutMs overall budget for connect + account/read.
*/
export async function probeCodexAppServerAuth(
config: CodexAppServerConfig,
websocketFn: CodexAppServerWebsocketFn | null,
timeoutMs = 8000
): Promise<CodexAppServerAuthStatus> {
if (!websocketFn) {
return { state: "unknown", reason: "websocket transport unavailable" };
}
const client = new CodexAppServerClient({ websocketFn, defaultTimeoutMs: timeoutMs });
const deadline = new Promise<CodexAppServerAuthStatus>((resolve) =>
setTimeout(() => resolve({ state: "unknown", reason: "auth probe timed out" }), timeoutMs)
);
const run = (async (): Promise<CodexAppServerAuthStatus> => {
try {
await client.connect(config.url, config.token);
await client.request(
"initialize",
{
clientInfo: { name: "omniroute-codex-app-server-health", title: null, version: "1.0" },
capabilities: null,
},
timeoutMs
);
// account/read: authenticated → { account: {...} }; logged out → no account.
const result = (await client.request("account/read", {}, timeoutMs)) as AccountReadResult;
const account = result?.account;
if (account && typeof account === "object") {
return {
state: "authenticated",
account: {
type: str(account.type),
email: str(account.email),
planType: str(account.planType),
},
};
}
return {
state: "logged_out",
reason: "app-server reachable but its Codex CLI is not signed in",
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
// A JSON-RPC error on account/read (e.g. AuthRequiredError) also means
// "up but not authenticated" — surface it as logged_out, not unknown, so
// the dashboard offers "Sign in with ChatGPT" rather than a scary error.
if (/auth|login|sign|unauthor|401/i.test(message)) {
return { state: "logged_out", reason: message };
}
return { state: "unknown", reason: message };
} finally {
client.close();
}
})();
return Promise.race([run, deadline]);
}

View File

@@ -0,0 +1,289 @@
/**
* Id-correlated JSON-RPC 2.0 client over a single WebSocket, for the Codex
* app-server transport.
*
* Ported from the stdio JSON-RPC pattern in `devin-cli-agentic.ts` (monotonic id,
* pending-request map settled on responses, notification vs response
* discrimination, settle-once) onto the wreq-js WebSocket transport used by the
* existing Codex WS path.
*
* The critical addition over the other transports is a catch-all handler for
* server -> client ServerRequests: the app-server can ask the client to approve a
* command / patch / permission. OmniRoute is a ROUTER — the harness that consumes
* it owns tool execution and policy — so codex must never stall a turn on its own
* interactive approval. Every inbound ServerRequest is always answered: approval
* prompts are auto-APPROVED (so the model's agentic tool calls proceed; the harness
* decides what really runs), and anything else we can't service gets a JSON-RPC
* error so the id is always settled and the turn never hangs.
*/
// wreq-js WebSocket surface (mirrors the private type in codex.ts:71-77).
export type CodexWreqWebSocket = {
send: (data: string) => void;
close: (code?: number, reason?: string) => void;
onmessage: ((event: { data: unknown }) => void) | null;
onerror: ((event: { message?: string }) => void) | null;
onclose: (() => void) | null;
};
export type CodexAppServerWebsocketFn = (
url: string,
opts?: Record<string, unknown>
) => Promise<CodexWreqWebSocket>;
interface PendingReq {
resolve: (result: unknown) => void;
reject: (err: Error) => void;
}
// The set of ServerRequest methods that are approval prompts (see PROTOCOL-DIGEST
// "Server -> client REQUESTS"). All of these get an auto-denial decision.
const APPROVAL_REQUEST_METHODS = new Set<string>([
"item/commandExecution/requestApproval",
"item/fileChange/requestApproval",
"item/permissions/requestApproval",
"applyPatchApproval",
"execCommandApproval",
]);
const ROUTER_APPROVAL_NOTE = "router: harness-controlled execution";
export interface CodexAppServerClientOptions {
/** Transport factory. Defaults to the shared wreq-js websocket() when omitted. */
websocketFn?: CodexAppServerWebsocketFn | null;
/** Default per-request timeout (ms). */
defaultTimeoutMs?: number;
}
/**
* The app-server → client REQUEST method by which codex invokes a harness-defined
* (dynamic) function tool. See appServerEvents.ts:CODEX_APPSERVER_TOOL_CALL_METHOD.
* A stateless router cannot execute the harness's tool, so this is handled by a
* PASSTHROUGH handler (surface it as a Responses function_call and complete the
* turn) rather than by the default -32601 rejection.
*/
const TOOL_CALL_REQUEST_METHOD = "item/tool/call";
/**
* Handler for a server → client `item/tool/call` ServerRequest. It receives the
* JSON-RPC id and raw params (DynamicToolCallParams). It OWNS settling the id
* (call `respond`/`respondError`) so the socket never hangs. Returning lets the
* executor emit tool_call_* AdapterEvents + complete the turn.
*/
export type CodexAppServerToolCallHandler = (
id: number,
params: unknown,
api: {
/** Settle the request id with a JSON-RPC result (a DynamicToolCallResponse). */
respond: (result: unknown) => void;
/** Settle the request id with a JSON-RPC error. */
respondError: (code: number, message: string) => void;
}
) => void;
export class CodexAppServerClient {
private ws: CodexWreqWebSocket | null = null;
private nextId = 1;
private readonly pending = new Map<number, PendingReq>();
private notificationHandler: (method: string, params: unknown) => void = () => {};
private toolCallHandler: CodexAppServerToolCallHandler | null = null;
private readonly websocketFn: CodexAppServerWebsocketFn | null;
private readonly defaultTimeoutMs: number;
private closed = false;
constructor(options: CodexAppServerClientOptions = {}) {
this.websocketFn = options.websocketFn ?? null;
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 120_000;
}
/**
* Open the WebSocket and attach the capability token as `Authorization: Bearer`.
* Do NOT add any chatgpt.com Origin/WS header normalization here — the local
* app-server wants only the Authorization header.
*/
async connect(url: string, token: string): Promise<void> {
if (!this.websocketFn) {
throw new Error("Codex app-server websocket transport unavailable");
}
// wreq-js's websocket() REQUIRES a browser/os impersonation profile alongside
// headers — the same shape the existing Codex WS path uses (codex.ts:980).
// Omitting browser/os makes the native call hang/throw, so the app-server
// turn never connects. The local app-server ignores the impersonation
// fingerprint; only the Authorization bearer matters for its ws-auth.
this.ws = await this.websocketFn(url, {
browser: "chrome_142",
os: "windows",
headers: { Authorization: `Bearer ${token}` },
});
this.ws.onmessage = (event) => this.onFrame(event.data);
this.ws.onerror = (event) => this.failAll(event?.message ?? "app-server socket error");
this.ws.onclose = () => this.failAll("app-server connection closed");
}
/** Send a ClientRequest and resolve when its id-matched response arrives. */
request<T = unknown>(method: string, params: unknown, timeoutMs = this.defaultTimeoutMs): Promise<T> {
const id = this.nextId++;
return new Promise<T>((resolve, reject) => {
if (!this.ws || this.closed) {
reject(new Error(`Cannot send ${method}: app-server connection is not open`));
return;
}
const timer = setTimeout(() => {
this.pending.delete(id);
reject(new Error(`Codex app-server request "${method}" timed out after ${timeoutMs}ms`));
}, timeoutMs);
this.pending.set(id, {
resolve: (result) => {
clearTimeout(timer);
resolve(result as T);
},
reject: (err) => {
clearTimeout(timer);
reject(err);
},
});
this.ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
});
}
/** Send a ClientNotification (no id, no reply expected — e.g. turn/interrupt). */
notify(method: string, params: unknown): void {
if (!this.ws || this.closed) return;
this.ws.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
}
/** Register the handler that receives server -> client NOTIFICATIONS (no id). */
onNotification(fn: (method: string, params: unknown) => void): void {
this.notificationHandler = fn;
}
/**
* Register the handler for the `item/tool/call` server → client ServerRequest
* (a harness function-tool invocation). When set, `item/tool/call` is routed to
* this handler INSTEAD of the default -32601 rejection; the handler must settle
* the id via the provided `respond`/`respondError`. When unset, `item/tool/call`
* falls through to the default rejection (keeps the turn unstuck).
*/
onToolCall(fn: CodexAppServerToolCallHandler): void {
this.toolCallHandler = fn;
}
close(): void {
if (this.closed) return;
this.closed = true;
try {
this.ws?.close(1000, "done");
} catch {
/* socket close race — ignore */
}
}
/** Parse one inbound frame and dispatch by JSON-RPC shape. */
private onFrame(raw: unknown): void {
let msg: Record<string, unknown>;
try {
const line = typeof raw === "string" ? raw : Buffer.from(raw as Uint8Array).toString("utf8");
msg = JSON.parse(line) as Record<string, unknown>;
} catch {
// A non-JSON frame is unusable; drop it rather than crash the socket.
return;
}
const hasId = msg.id !== undefined && msg.id !== null;
const hasMethod = typeof msg.method === "string";
if (hasId && !hasMethod) {
// A RESPONSE to one of our ClientRequests → settle the pending map.
const id = msg.id as number;
const pending = this.pending.get(id);
if (!pending) return;
this.pending.delete(id);
if (msg.error) {
const err = msg.error as { code?: unknown; message?: unknown };
pending.reject(new Error(`${String(err.code ?? "error")}: ${String(err.message ?? "unknown")}`));
} else {
pending.resolve(msg.result);
}
return;
}
if (hasMethod && hasId) {
// A server -> client REQUEST → we MUST reply with the matching id or the turn stalls.
const id = msg.id as number;
const method = msg.method as string;
// A harness function-tool invocation is routed to the passthrough handler
// (if registered) so the executor can surface it as a Responses function_call
// and complete the turn. The handler owns settling the id.
if (method === TOOL_CALL_REQUEST_METHOD && this.toolCallHandler) {
this.toolCallHandler(id, msg.params, {
respond: (result) => this.respondToRequest(id, result),
respondError: (code, message) => this.respondErrorToRequest(id, code, message),
});
return;
}
this.answerServerRequest(id, method);
return;
}
if (hasMethod) {
// A server -> client NOTIFICATION → hand to the stream.
this.notificationHandler(msg.method as string, msg.params);
}
}
/**
* Always answer an inbound ServerRequest so its id is settled. Approval prompts
* are auto-APPROVED (OmniRoute is a router; the harness that consumes it owns
* execution policy, so codex's own approval must not block the turn). Anything
* we cannot service gets a JSON-RPC error so the id is still settled.
*/
private answerServerRequest(id: number, method: string): void {
if (!this.ws || this.closed) return;
if (APPROVAL_REQUEST_METHODS.has(method)) {
// ReviewDecision "approved" — let the model's agentic action proceed. The
// harness downstream of OmniRoute is the real gate. Note the note field is
// advisory; the decision string is what codex acts on.
this.ws.send(
JSON.stringify({
jsonrpc: "2.0",
id,
result: { decision: "approved", note: ROUTER_APPROVAL_NOTE },
})
);
return;
}
// Non-approval server request we do not service here: reject the id so the
// app-server does not wait on us (belt-and-suspenders; keeps turns unstuck).
this.ws.send(
JSON.stringify({
jsonrpc: "2.0",
id,
error: {
code: -32601,
message: `router: unsupported server request "${method}"`,
},
})
);
}
/** Settle an inbound ServerRequest id with a JSON-RPC result. */
private respondToRequest(id: number, result: unknown): void {
if (!this.ws || this.closed) return;
this.ws.send(JSON.stringify({ jsonrpc: "2.0", id, result }));
}
/** Settle an inbound ServerRequest id with a JSON-RPC error. */
private respondErrorToRequest(id: number, code: number, message: string): void {
if (!this.ws || this.closed) return;
this.ws.send(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }));
}
private failAll(reason: string): void {
const err = new Error(reason);
for (const [id, pending] of this.pending.entries()) {
this.pending.delete(id);
pending.reject(err);
}
this.notificationHandler("__transport_closed__", { reason });
}
}

View File

@@ -0,0 +1,94 @@
import { readFileSync } from "node:fs";
/**
* Resolved connection config for the Codex app-server WS transport.
*
* The app-server is a locally-running `codex app-server` process reachable over a
* single WebSocket speaking JSON-RPC 2.0. It self-manages OpenAI auth + model
* routing; the ONLY credential OmniRoute presents is the capability token, sent as
* `Authorization: Bearer <hex>` on the WS handshake.
*/
export interface CodexAppServerConfig {
/** ws:// or wss:// URL of the app-server (e.g. "ws://ts-egress:1456"). */
url: string;
/** Capability token (hex string) sent as `Authorization: Bearer <token>`. */
token: string;
/** Working directory passed to `thread/start { cwd }` inside the codex container. */
cwd: string;
/**
* Optional codex approval policy override (AskForApproval). Defaults to "never"
* in the executor so codex runs non-interactively and never blocks the turn on
* its own approval — the harness that consumes OmniRoute owns execution policy.
*/
approvalPolicy?: string;
/**
* Optional codex sandbox override (SandboxMode). Defaults to "danger-full-access"
* in the executor so codex's own sandbox does not gate the model; the harness is
* the real gate. Callers may tighten this per request via providerSpecificData.
*/
sandbox?: string;
}
type ProviderSpecificData = Record<string, unknown> | null | undefined;
function firstString(...values: unknown[]): string | null {
for (const value of values) {
if (typeof value === "string" && value.trim().length > 0) return value.trim();
}
return null;
}
/**
* Read the capability token, preferring an inline token, then a token FILE path.
* The token file (produced by `codex app-server --ws-token-file <path>`) holds the
* same hex string that is presented as the bearer token.
*/
function resolveToken(psd: ProviderSpecificData): string | null {
const inline = firstString(
psd?.codexAppServerToken,
process.env.OMNIROUTE_CODEX_APPSERVER_WS_TOKEN
);
if (inline) return inline;
const tokenFile = firstString(
psd?.codexAppServerTokenFile,
process.env.OMNIROUTE_CODEX_APPSERVER_WS_TOKEN_FILE
);
if (!tokenFile) return null;
try {
const contents = readFileSync(tokenFile, "utf8").trim();
return contents.length > 0 ? contents : null;
} catch {
return null;
}
}
function isWebSocketUrl(url: string): boolean {
return url.startsWith("ws://") || url.startsWith("wss://");
}
/**
* Resolve the app-server connection config from providerSpecificData with env
* fallbacks. Returns `null` when not fully configured (URL + token both required)
* so the gating predicate `isCodexAppServerRequired` stays false and Codex falls
* back to its other transports.
*/
export function resolveAppServerConfig(psd: ProviderSpecificData): CodexAppServerConfig | null {
const url = firstString(psd?.codexAppServerUrl, process.env.OMNIROUTE_CODEX_APPSERVER_WS);
if (!url || !isWebSocketUrl(url)) return null;
const token = resolveToken(psd);
if (!token) return null;
const cwd =
firstString(psd?.codexAppServerCwd, process.env.OMNIROUTE_CODEX_APPSERVER_CWD) ?? "/tmp";
const approvalPolicy =
firstString(psd?.codexAppServerApprovalPolicy, process.env.OMNIROUTE_CODEX_APPSERVER_APPROVAL) ??
undefined;
const sandbox =
firstString(psd?.codexAppServerSandbox, process.env.OMNIROUTE_CODEX_APPSERVER_SANDBOX) ??
undefined;
return { url, token, cwd, ...(approvalPolicy ? { approvalPolicy } : {}), ...(sandbox ? { sandbox } : {}) };
}

View File

@@ -0,0 +1,208 @@
import type { AdapterEvent, CodexUsage } from "../../vendor/codex-chatgpt-web/types.ts";
/**
* Map Codex app-server JSON-RPC notifications onto the AdapterEvent stream that
* `bridgeToResponsesSSE` / `buildResponseJSON` consume.
*
* Wire method names are the slash-notation ServerNotification variants verified
* from the real codex binary (see PROTOCOL-DIGEST.md). Only the handful needed for
* a plain text turn are mapped; everything else is ignored.
*
* The `*Notification` param TYPES referenced below (adapted from the ts-rs bindings):
* AgentMessageDeltaNotification { threadId, turnId, itemId, delta }
* ReasoningTextDeltaNotification { threadId, turnId, itemId, delta, contentIndex }
* TurnCompletedNotification { threadId, turn } (turn carries usage)
* ErrorNotification { error, willRetry, threadId, turnId }
*/
// Wire method names (slash-notation) → intent. Kept as named constants so a typo
// can't silently break the mapping.
export const CODEX_APPSERVER_METHODS = {
agentMessageDelta: "item/agentMessage/delta",
reasoningTextDelta: "item/reasoning/textDelta",
reasoningSummaryTextDelta: "item/reasoning/summaryTextDelta",
turnCompleted: "turn/completed",
error: "error",
} as const;
/**
* The app-server → client REQUEST method by which codex invokes a harness-defined
* (dynamic) function tool. It is NOT a notification: it is a server→client
* ServerRequest that BLOCKS the codex turn waiting for a `DynamicToolCallResponse`
* with the tool's output.
*
* `params` shape = `DynamicToolCallParams` (ts-rs binding):
* { threadId, turnId, callId, namespace: string | null, tool: string, arguments: JsonValue }
*
* OmniRoute is a STATELESS ROUTER: it cannot execute the harness's tool (the tool
* body lives in the harness downstream, not here). So instead of "executing" the
* call, we PASS IT THROUGH: emit tool_call_* AdapterEvents so the bridge renders a
* Responses `function_call` output item, then complete the turn. The harness runs
* the tool and replays the result in a fresh /v1/responses request (the same
* stateless-full-history contract every other OmniRoute provider uses).
*/
export const CODEX_APPSERVER_TOOL_CALL_METHOD = "item/tool/call";
/** Minimal shape of the DynamicToolCallParams we consume for the passthrough. */
export interface DynamicToolCallLike {
callId?: unknown;
namespace?: unknown;
tool?: unknown;
arguments?: unknown;
}
/**
* The wire name the bridge's `toolNsMap` is keyed by: namespaced (MCP) tools are
* flattened to "<namespace>__<name>". codex sends the namespace + tool separately
* on DynamicToolCallParams, so we reconstruct the flat name for the round-trip.
*/
export function dynamicToolWireName(namespace: unknown, tool: unknown): string {
const name = typeof tool === "string" ? tool : "";
return typeof namespace === "string" && namespace.length > 0
? `${namespace}__${name}`
: name;
}
/**
* Translate ONE codex `item/tool/call` ServerRequest into the tool_call_* AdapterEvent
* triple the bridge already knows how to turn into a Responses function_call /
* custom_tool_call / tool_search_call (see bridge.ts:700-784). The `arguments` are
* serialized to a JSON string (the bridge accumulates `tool_call_delta.arguments`
* as a string and JSON.parses it at close).
*
* This emits the COMPLETE call in one shot (start → delta → end) because the
* server-request carries the fully-formed arguments (codex does not stream dynamic
* tool-call arguments to the client the way the chatgpt-web adapter streams native
* ones). The caller is responsible for then completing the turn.
*/
export function translateToolCall(
params: DynamicToolCallLike,
push: (event: AdapterEvent) => void
): void {
const callId =
typeof params.callId === "string" && params.callId.length > 0
? params.callId
: `call_${Math.random().toString(36).slice(2)}`;
const name = dynamicToolWireName(params.namespace, params.tool);
let argsStr = "{}";
const rawArgs = params.arguments;
if (typeof rawArgs === "string") {
argsStr = rawArgs.length > 0 ? rawArgs : "{}";
} else if (rawArgs !== undefined && rawArgs !== null) {
try {
argsStr = JSON.stringify(rawArgs);
} catch {
argsStr = "{}";
}
}
push({ type: "tool_call_start", id: callId, name });
if (argsStr.length > 0) push({ type: "tool_call_delta", arguments: argsStr });
push({ type: "tool_call_end" });
}
interface RawUsage {
input_tokens?: number;
cached_input_tokens?: number;
output_tokens?: number;
reasoning_output_tokens?: number;
total_tokens?: number;
}
/** Extract a numeric field defensively (the wire may omit or null it). */
function num(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
/**
* Convert the app-server usage shape (snake_case token counts) into the canonical
* CodexUsage the bridge expects. Returns undefined when nothing usable is present.
*/
export function mapUsage(raw: unknown): CodexUsage | undefined {
if (!raw || typeof raw !== "object") return undefined;
const u = raw as RawUsage;
const inputTokens = num(u.input_tokens) ?? 0;
const outputTokens = num(u.output_tokens) ?? 0;
const usage: CodexUsage = { inputTokens, outputTokens };
const cached = num(u.cached_input_tokens);
if (cached !== undefined) {
usage.cachedInputTokens = cached;
usage.cacheReadInputTokens = cached;
}
const reasoning = num(u.reasoning_output_tokens);
if (reasoning !== undefined) usage.reasoningOutputTokens = reasoning;
const total = num(u.total_tokens);
if (total !== undefined) usage.totalTokens = total;
return usage;
}
/**
* Pull a usage object out of a `turn/completed` param. The Turn payload carries
* token counts; different app-server builds nest it under `usage` or `tokenUsage`,
* so probe both before giving up.
*/
function extractTurnUsage(params: Record<string, unknown>): CodexUsage | undefined {
const turn = params.turn;
if (turn && typeof turn === "object") {
const t = turn as Record<string, unknown>;
return mapUsage(t.usage) ?? mapUsage(t.tokenUsage) ?? mapUsage(t.token_usage);
}
return mapUsage(params.usage);
}
function errorMessage(params: Record<string, unknown>): string {
const err = params.error;
if (err && typeof err === "object") {
const m = (err as Record<string, unknown>).message;
if (typeof m === "string" && m.length > 0) return m;
}
if (typeof params.message === "string" && params.message.length > 0) return params.message;
return "Codex app-server reported an error";
}
/**
* Translate one notification into AdapterEvent(s) and push them into the queue.
*
* Returns `true` when the notification is terminal (turn/completed or error), so
* the caller can close the event queue after draining.
*/
export function translateNotification(
method: string,
params: unknown,
push: (event: AdapterEvent) => void
): boolean {
const p = (params && typeof params === "object" ? params : {}) as Record<string, unknown>;
switch (method) {
case CODEX_APPSERVER_METHODS.agentMessageDelta: {
const delta = p.delta;
if (typeof delta === "string" && delta.length > 0) {
push({ type: "text_delta", text: delta });
}
return false;
}
case CODEX_APPSERVER_METHODS.reasoningTextDelta:
case CODEX_APPSERVER_METHODS.reasoningSummaryTextDelta: {
const delta = p.delta;
if (typeof delta === "string" && delta.length > 0) {
push({ type: "thinking_delta", thinking: delta });
}
return false;
}
case CODEX_APPSERVER_METHODS.turnCompleted: {
push({ type: "done", usage: extractTurnUsage(p), endTurn: true });
return true;
}
case CODEX_APPSERVER_METHODS.error: {
push({
type: "error",
message: errorMessage(p),
status: 502,
errorType: "provider_error",
code: "codex_app_server_turn_failed",
});
return true;
}
default:
return false;
}
}

View File

@@ -7,6 +7,7 @@ import { GheCopilotExecutor } from "./ghe-copilot.ts";
import { QoderExecutor } from "./qoder.ts";
import { KiroExecutor } from "./kiro.ts";
import { CodexExecutor } from "./codex.ts";
import { CodexAppServerExecutor } from "./codex-app-server.ts";
import { CursorExecutor } from "./cursor.ts";
import { TraeExecutor } from "./trae.ts";
import { DefaultExecutor } from "./default.ts";
@@ -97,6 +98,7 @@ const executors = {
"amazon-q": new KiroExecutor("amazon-q"),
bedrock: new BedrockExecutor(),
codex: new CodexExecutor(),
"codex-app-server": new CodexAppServerExecutor({}, "codex-app-server"),
"chatgpt-web-codex": new ChatGptWebCodexExecutor(),
"cgpt-codex": new ChatGptWebCodexExecutor(),
cursor: new CursorExecutor(),

View File

@@ -187,6 +187,12 @@ export function engineToCompressFn(engineId: string): CompressFn {
return async (text: string): Promise<string> => {
const body: Record<string, unknown> = {
messages: [{ role: "user", content: text }],
// #7746 follow-up: CCR only compresses for callers that advertise the
// omniroute_ccr_retrieve tool (otherwise its content-addressed marker is
// unresolvable). Real CCR traffic always carries this tool, so the
// benchmark must too, or CCR measures as a no-op. Other engines ignore
// the `tools` field, so this is inert for them.
tools: [{ type: "function", function: { name: "omniroute_ccr_retrieve" } }],
};
try {
@@ -199,6 +205,16 @@ export function engineToCompressFn(engineId: string): CompressFn {
const messages = result.body["messages"];
if (Array.isArray(messages) && messages.length > 0) {
// CCR may inject a leading [CCR protocol] system instruction, so the
// compressed user text is not necessarily messages[0]. Prefer the LAST
// message with string content (the user turn we fed in); fall back to
// the first string content otherwise.
for (let i = messages.length - 1; i >= 0; i--) {
const c = (messages[i] as Record<string, unknown>)["content"];
if (typeof c === "string" && (messages[i] as Record<string, unknown>)["role"] !== "system") {
return c;
}
}
const content = (messages[0] as Record<string, unknown>)["content"];
if (typeof content === "string") return content;
}

View File

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

View File

@@ -409,7 +409,7 @@ export default function ConnectionsListPanel({
? (enabled) => handleToggleConnectionAutoSync(conn.id, enabled)
: undefined
}
isCodex={providerId === "codex"}
isCodex={providerId === "codex" || providerId === "codex-app-server"}
isCcCompatible={isCcCompatible}
cliproxyapiEnabled={cpaProviderEnabled}
onToggleCliproxyapiMode={(enabled) => handleToggleCliproxyapiMode(conn.id, enabled)}
@@ -610,7 +610,7 @@ export default function ConnectionsListPanel({
? (enabled) => handleToggleConnectionAutoSync(conn.id, enabled)
: undefined
}
isCodex={providerId === "codex"}
isCodex={providerId === "codex" || providerId === "codex-app-server"}
isCcCompatible={isCcCompatible}
cliproxyapiEnabled={cpaProviderEnabled}
onToggleCliproxyapiMode={(enabled) =>

View File

@@ -1,10 +1,22 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { ensureCliConfigWriteAllowed } from "@/shared/services/cliRuntime";
import { CodexAuthFileError, writeCodexAuthFileToLocalCli } from "@/lib/oauth/utils/codexAuthFile";
import {
CodexAuthFileError,
writeCodexAuthFileToLocalCliIfNeeded,
} from "@/lib/oauth/utils/codexAuthFile";
import { getAuditRequestContext, logAuditEvent } from "@/lib/compliance/index";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
// Optional body { force?: boolean }. Unknown keys are stripped rather than
// rejected so the endpoint stays tolerant of the empty/no-body calls it
// historically accepted. Non-boolean `force` is coerced away to the default.
const ApplyLocalBodySchema = z
.object({ force: z.boolean().optional() })
.partial()
.passthrough();
function toErrorResponse(error: unknown) {
if (error instanceof CodexAuthFileError) {
return NextResponse.json(
@@ -33,7 +45,21 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
}
const { id } = await params;
const result = await writeCodexAuthFileToLocalCli(id);
// Optional { force?: boolean } body. By default we DON'T clobber an existing,
// fresh ~/.codex/auth.json (a session the user may be managing themselves);
// force overwrites it (a backup is always taken regardless). Malformed/empty
// bodies are tolerated — this endpoint historically took no body.
let force = false;
try {
const parsed = ApplyLocalBodySchema.safeParse(await request.json());
force = parsed.success ? parsed.data.force === true : false;
} catch {
/* no body — default force=false */
}
const applied = await writeCodexAuthFileToLocalCliIfNeeded(id, { force });
const result = applied.result;
logAuditEvent({
action: "provider.credentials.applied",
@@ -45,18 +71,21 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
requestId: auditContext.requestId,
metadata: {
provider: "codex",
authPath: result.authPath,
savedBakPath: result.savedBakPath,
decision: applied.decision,
authPath: applied.authPath,
savedBakPath: result?.savedBakPath,
},
});
return NextResponse.json({
success: true,
connectionId: id,
connectionLabel: result.connectionLabel,
authPath: result.authPath,
savedBakPath: result.savedBakPath,
centralizedBackupPath: result.centralizedBackupPath,
// "skipped_present_fresh" means an existing healthy auth.json was kept.
decision: applied.decision,
connectionLabel: result?.connectionLabel,
authPath: applied.authPath,
savedBakPath: result?.savedBakPath,
centralizedBackupPath: result?.centralizedBackupPath,
writtenAt: new Date().toISOString(),
});
} catch (error) {

View File

@@ -0,0 +1,136 @@
/**
* Build the structured diagnosis object the connection-test route returns.
* Lives here (rather than inline in test/route.ts) so both the route and the
* codex-app-server health probe share one definition. Pure.
*/
export function makeDiagnosis(
type: string,
source: string,
message: string | null,
code: string | null = null
) {
return {
type,
source,
message: message || null,
code: code ?? null,
};
}
export type CodexAppServerHealth = {
valid: boolean;
error?: string;
diagnosis: unknown;
refreshed: boolean;
};
/**
* A codex "app-server" connection (providerSpecificData.codexTransport ===
* "app-server") does NOT carry a validatable OpenAI token: it drives the codex
* CLI's own `codex app-server` process over JSON-RPC/WebSocket, and THAT process
* self-manages its OpenAI OAuth (its own ~/.codex/auth.json), exactly like an
* interactive codex session. So the ordinary OAuth token probe is meaningless for
* these connections — it validates a placeholder and reports a false "Token
* invalid or revoked" 401 (which then trips the rate-limit cooldown on retest).
*
* The correct health signal for this transport is whether the app-server itself
* is reachable and ready. The app-server exposes an unauthenticated liveness
* endpoint at <httpBase>/readyz (200 = ready) alongside its ws:// listener, so we
* derive the http(s) origin from the configured ws(s):// URL and probe /readyz.
* Returns null when this connection is NOT an app-server connection (so the caller
* falls through to the normal token validation).
*/
export async function testCodexAppServerConnection(
connection: any
): Promise<CodexAppServerHealth | null> {
const psd = (connection?.providerSpecificData as Record<string, unknown> | undefined) || undefined;
// Fire the /readyz probe when EITHER (a) the connection opted into the
// app-server transport via the per-connection flag (a `codex` provider
// connection with codexTransport==="app-server"), OR (b) this is the
// first-class `codex-app-server` provider, which is app-server by definition
// and needs no flag. Otherwise return null so the caller falls through to the
// normal OAuth/apikey token validation.
const isAppServerProvider = connection?.provider === "codex-app-server";
const isAppServerFlag = psd?.codexTransport === "app-server";
if (!isAppServerProvider && !isAppServerFlag) return null;
// Dynamic import (not a static top-level import) so this executor-config module
// stays behind the open-sse boundary the no-restricted-imports lint rule enforces.
const { resolveAppServerConfig } = await import(
"@omniroute/open-sse/executors/codex/appServerConfig.ts"
);
const config = resolveAppServerConfig(psd);
if (!config) {
const error = "Codex app-server transport is not configured (missing url or token)";
return {
valid: false,
error,
refreshed: false,
diagnosis: makeDiagnosis("validation_error", "local", error, "app_server_unconfigured"),
};
}
// ws://host:port → http://host:port/readyz ; wss:// → https://.
const httpBase = config.url.replace(/^ws(s?):\/\//i, (_m, s) => `http${s}://`).replace(/\/+$/, "");
const readyzUrl = `${httpBase}/readyz`;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 8000);
try {
const res = await fetch(readyzUrl, {
method: "GET",
headers: { Authorization: `Bearer ${config.token}` },
signal: controller.signal,
});
if (res.status !== 200) {
const error = `Codex app-server not ready (${readyzUrl} → HTTP ${res.status})`;
return {
valid: false,
error,
refreshed: false,
diagnosis: makeDiagnosis("provider_error", "app_server", error, "app_server_not_ready"),
};
}
// The server PROCESS is up. Now confirm its Codex CLI is actually SIGNED IN —
// /readyz alone would show green for a logged-out CLI, which then fails on the
// first real turn. Probe account/read over the JSON-RPC WebSocket.
let authStatus;
try {
const [{ probeCodexAppServerAuth }, { getCodexAppServerWebsocketTransport }] =
await Promise.all([
import("@omniroute/open-sse/executors/codex/appServerAuthProbe.ts"),
import("@omniroute/open-sse/executors/codex.ts"),
]);
authStatus = await probeCodexAppServerAuth(config, getCodexAppServerWebsocketTransport(), 8000);
} catch (probeErr: any) {
// If the auth probe itself fails to load/run, don't fail the whole health
// check — the server IS reachable. Treat as unknown-but-reachable (valid).
authStatus = { state: "unknown", reason: probeErr?.message ?? "auth probe failed" } as const;
}
if (authStatus.state === "logged_out") {
const error =
"Codex app-server is running but its Codex CLI is not signed in. Use \u201cSign in with ChatGPT\u201d to authenticate.";
return {
valid: false,
error,
refreshed: false,
diagnosis: makeDiagnosis("auth_required", "app_server", error, "app_server_login_required"),
};
}
// "authenticated" → healthy; "unknown" (probe unavailable/timed out) → treat
// the reachable server as healthy rather than blocking on an inconclusive probe.
return { valid: true, refreshed: false, diagnosis: null };
} catch (err: any) {
const reason = err?.name === "AbortError" ? "timed out" : (err?.message ?? "unreachable");
const error = `Codex app-server unreachable (${readyzUrl}: ${reason})`;
return {
valid: false,
error,
refreshed: false,
diagnosis: makeDiagnosis("provider_error", "app_server", error, "app_server_unreachable"),
};
} finally {
clearTimeout(timer);
}
}

View File

@@ -28,6 +28,7 @@ import {
} from "@/lib/oauth/gitlab";
import { providerAllowsOptionalApiKey } from "@/shared/constants/providers";
import { shouldUseApiKeyConnectionTest } from "./webSessionTestDispatch";
import { testCodexAppServerConnection, makeDiagnosis } from "./codexAppServerHealth";
import { removeConnectionHealth } from "@omniroute/open-sse/services/apiKeyRotator.ts";
import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation";
import { classifyAmbiguousOrAuthError, type ClassifyFailureArgs } from "./mistralAmbiguousAuth";
@@ -52,20 +53,6 @@ function toSafeMessage(value: any, fallback = "Unknown error"): string {
return trimmed || fallback;
}
function makeDiagnosis(
type: string,
source: string,
message: string | null,
code: string | null = null
) {
return {
type,
source,
message: message || null,
code: code ?? null,
};
}
/**
* A provider/account that the upstream has deactivated (vs. a revoked/expired token).
* #1444: a Codex account can have a perfectly healthy OAuth refresh while its ChatGPT
@@ -1024,6 +1011,13 @@ export async function testSingleConnection(connectionId: string, validationModel
const startTime = Date.now();
const runtime = await getProviderRuntimeStatus(connection);
// Codex app-server connections carry no validatable OpenAI token (the codex
// app-server process self-manages its own OAuth). Probe the app-server's
// /readyz liveness endpoint instead of the meaningless token check — otherwise
// every sweep reports a false "Token invalid or revoked" 401 and cools the
// connection down. Returns null for non-app-server connections (fall through).
const appServerResult = await testCodexAppServerConnection(connection);
if ((runtime as any)?.diagnosis) {
result = {
valid: false,
@@ -1031,6 +1025,10 @@ export async function testSingleConnection(connectionId: string, validationModel
refreshed: false,
diagnosis: (runtime as any).diagnosis,
};
} else if (appServerResult) {
result = await runWithProxyContext(proxyInfo?.proxy || null, () =>
Promise.resolve(appServerResult)
);
} else if (shouldUseApiKeyConnectionTest(connection.authType, provider)) {
const enrichedConnection = validationModelId
? {

View File

@@ -13873,5 +13873,12 @@
"toolsMismatch": "O provedor nao suporta chamada de ferramentas",
"structuredOutputMismatch": "O provedor nao suporta saida estruturada",
"contextWindowMismatch": "A requisicao excede a janela de contexto do provedor"
},
"cheaperInferenceSponsorBanner": {
"title": "Cheaper Inference é um Amigo Open Source do OmniRoute",
"description": "Um gateway ordenado por custo que revende dezenas de modelos de fronteira atrás de um único endpoint compatível com OpenAI, roteando cada requisição para o provedor elegível mais barato, nunca acima do preço de tabela.",
"cta": "Obter uma chave de API",
"partnerLinkNote": "Link de parceiro",
"dismissAriaLabel": "Dispensar"
}
}

View File

@@ -6,6 +6,10 @@ export interface NodeSqliteDatabaseLike {
run(...p: unknown[]): { changes: number | bigint; lastInsertRowid: number | bigint };
get(...p: unknown[]): unknown;
all(...p: unknown[]): unknown[];
// node:sqlite (DatabaseSync) statements expose these tuning setters. They
// are optional here so the shared adapter also accepts lighter test doubles.
setAllowUnknownNamedParameters?(enabled: boolean): void;
setAllowBareNamedParameters?(enabled: boolean): void;
};
exec(sql: string): void;
close(): void;
@@ -13,6 +17,46 @@ export interface NodeSqliteDatabaseLike {
const MAX_STMT_CACHE_SIZE = 200;
// node:sqlite hands back rows whose prototype is `null` (Object.create(null)),
// whereas better-sqlite3 (the driver we ship and run in production/CI) returns
// ordinary Object.prototype rows. The difference is invisible for normal
// property access but breaks callers that compare rows with structural
// equality that also checks the prototype (e.g. Node's assert.deepStrictEqual,
// used by unit tests written against the better-sqlite3 row shape). Normalize
// every row to a plain object so the node:sqlite fallback is behaviourally
// identical to the native better-sqlite3 path.
function toPlainRow<T>(row: T): T {
if (row === null || typeof row !== "object") return row;
return { ...(row as Record<string, unknown>) } as T;
}
// better-sqlite3 (the production/CI driver) and sql.js both accept `undefined`
// as a bound value and treat it as SQL NULL. node:sqlite is stricter and throws
// "Provided value cannot be bound to SQLite parameter N" for undefined. Several
// call sites pass undefined for absent optional columns (e.g. a capability sync
// that omits modalities_input), so coerce undefined -> null here to keep the
// node:sqlite fallback behaviourally compatible with the native driver. This
// handles both positional params and a single named-params object.
function normalizeBindParams(params: unknown[]): unknown[] {
const [first] = params;
const isLoneNamedParamsObject =
params.length === 1 &&
first !== null &&
typeof first === "object" &&
!Array.isArray(first) &&
!Buffer.isBuffer(first) &&
!(first instanceof Uint8Array);
if (isLoneNamedParamsObject) {
const source = first as Record<string, unknown>;
const normalized: Record<string, unknown> = {};
for (const key of Object.keys(source)) {
normalized[key] = source[key] === undefined ? null : source[key];
}
return [normalized];
}
return params.map((value) => (value === undefined ? null : value));
}
export function createNodeSqliteAdapterFromDatabase(
db: NodeSqliteDatabaseLike,
filePath: string,
@@ -41,6 +85,14 @@ export function createNodeSqliteAdapterFromDatabase(
stmtCache.set(sql, entry);
} else {
const stmt = db.prepare(sql);
// better-sqlite3 (the production/CI driver) silently ignores named
// parameters supplied in the bind object that the SQL text does not
// reference. node:sqlite instead throws "Unknown named parameter '<x>'".
// Several call sites deliberately pass a superset params object (e.g. an
// UPDATE that omits @createdAt while the shared params builder still
// includes it), so relax node:sqlite to match better-sqlite3 and keep the
// fallback driver behaviourally compatible.
stmt.setAllowUnknownNamedParameters?.(true);
if (stmtCache.size >= MAX_STMT_CACHE_SIZE) {
const oldestKey = stmtCache.keys().next().value;
if (oldestKey !== undefined) {
@@ -119,17 +171,19 @@ export function createNodeSqliteAdapterFromDatabase(
const stmt = getCached(sql);
return {
run(...params: unknown[]): RunResult {
const r = stmt.run(...params);
const r = stmt.run(...normalizeBindParams(params));
return {
changes: Number(r.changes ?? 0),
lastInsertRowid: Number(r.lastInsertRowid ?? 0),
};
},
get(...params: unknown[]): unknown {
return stmt.get(...params);
return toPlainRow(stmt.get(...normalizeBindParams(params)));
},
all(...params: unknown[]): unknown[] {
return stmt.all(...params);
return (stmt.all(...normalizeBindParams(params)) as unknown[]).map((row) =>
toPlainRow(row)
);
},
};
},

View File

@@ -6,6 +6,19 @@ import type { SqliteAdapter, PreparedStatement, RunResult } from "./types";
const SAVE_DEBOUNCE_MS = 100;
const CHECKPOINT_INTERVAL_MS = 60_000;
// sql.js's stmt.getAsObject() returns rows whose prototype is `null`
// (Object.create(null)), whereas better-sqlite3 (the driver we ship and run in
// production/CI) hands back ordinary Object.prototype rows. That difference is
// invisible for normal property access but breaks callers that compare rows
// with structural equality that also checks the prototype (e.g. Node's
// assert.deepStrictEqual, used by several unit tests written against the
// better-sqlite3 row shape). Normalize every row to a plain object so the
// sql.js fallback is behaviourally identical to the native better-sqlite3 path.
function toPlainRow<T>(row: T): T {
if (row === null || typeof row !== "object") return row;
return { ...(row as Record<string, unknown>) } as T;
}
let _sqlJsLib: Awaited<ReturnType<(typeof import("sql.js"))["default"]>> | null = null;
function resolveSqlJsWasmPath(): string {
@@ -240,7 +253,7 @@ export async function createSqlJsAdapter(filePath: string): Promise<SqliteAdapte
try {
const bindValue = toBindValue(params);
if (bindValue !== undefined) stmt.bind(bindValue);
if (stmt.step()) return stmt.getAsObject();
if (stmt.step()) return toPlainRow(stmt.getAsObject());
return undefined;
} finally {
stmt.free();
@@ -252,7 +265,7 @@ export async function createSqlJsAdapter(filePath: string): Promise<SqliteAdapte
const bindValue = toBindValue(params);
if (bindValue !== undefined) stmt.bind(bindValue);
const rows: unknown[] = [];
while (stmt.step()) rows.push(stmt.getAsObject());
while (stmt.step()) rows.push(toPlainRow(stmt.getAsObject()));
return rows;
} finally {
stmt.free();

View File

@@ -350,3 +350,89 @@ export async function writeCodexAuthFileToLocalCli(connectionId: string) {
centralizedBackupPath,
};
}
/**
* Decision for the guarded write (see writeCodexAuthFileToLocalCliIfNeeded).
*/
export type CodexAuthWriteDecision =
| "written" // wrote a fresh auth.json (was absent, stale, or force)
| "skipped_present_fresh"; // an existing, non-stale auth.json was left untouched
/**
* Guarded variant of writeCodexAuthFileToLocalCli for the codex-app-server
* "Sign in with ChatGPT" flow. Per the design decision (William, Q2):
*
* - Write ONLY when ~/.codex/auth.json is ABSENT, or STALE (its token is at/
* past the refresh buffer), or when `force` is set.
* - NEVER clobber an existing, healthy (non-stale) auth.json — a user may be
* managing the CLI session themselves. (The underlying writer always makes a
* backup regardless, so even a forced overwrite is recoverable.)
*
* Staleness is read from the existing file's `last_refresh` + the token's own
* expiry claim (JWT `exp` on the access_token) when present; if neither is
* readable we treat the file as fresh (do not clobber).
*
* Returns the write decision plus (when written) the underlying write result.
*/
export async function writeCodexAuthFileToLocalCliIfNeeded(
connectionId: string,
options: { force?: boolean } = {}
): Promise<{ decision: CodexAuthWriteDecision; authPath: string | null; result?: Awaited<ReturnType<typeof writeCodexAuthFileToLocalCli>> }> {
const paths = getCliConfigPaths("codex");
const authPath = paths?.auth ?? null;
if (!options.force && authPath) {
const existing = await readExistingCodexAuth(authPath);
if (existing && !isCodexAuthStale(existing)) {
// Present and healthy — do not clobber a session we didn't (or don't need
// to) manage. The connection can still authenticate turns via this file.
return { decision: "skipped_present_fresh", authPath };
}
}
const result = await writeCodexAuthFileToLocalCli(connectionId);
return { decision: "written", authPath: result.authPath, result };
}
/** Read + parse an existing ~/.codex/auth.json; null when absent/unreadable. */
async function readExistingCodexAuth(authPath: string): Promise<CodexAuthFilePayload | null> {
try {
const raw = await fs.readFile(authPath, "utf8");
const parsed = JSON.parse(raw) as unknown;
const rec = toRecord(parsed);
const tokens = toRecord(rec.tokens);
if (!toNonEmptyString(tokens.access_token)) return null;
return parsed as CodexAuthFilePayload;
} catch {
return null;
}
}
/**
* A stored auth.json is "stale" when its access_token is at/past the refresh
* buffer. Prefer the JWT `exp` claim on the access_token; fall back to
* `last_refresh` + a conservative validity window; if neither is parseable,
* treat as NOT stale (never clobber on ambiguity).
*/
function isCodexAuthStale(payload: CodexAuthFilePayload): boolean {
const accessToken = toNonEmptyString(payload?.tokens?.access_token);
if (accessToken) {
const claims = decodeJwtPayload(accessToken);
const exp = claims && typeof claims.exp === "number" ? claims.exp : null;
if (exp) {
const expiresAtMs = exp * 1000;
return expiresAtMs - Date.now() <= CODEX_REFRESH_BUFFER_MS;
}
}
// No usable exp claim — fall back to last_refresh age. Codex access tokens are
// short-lived (~hours); if the file hasn't refreshed in > 6h, consider it stale.
const lastRefresh = toNonEmptyString(payload?.last_refresh);
if (lastRefresh) {
const refreshedMs = new Date(lastRefresh).getTime();
if (!Number.isNaN(refreshedMs)) {
const SIX_HOURS_MS = 6 * 60 * 60 * 1000;
return Date.now() - refreshedMs >= SIX_HOURS_MS;
}
}
return false;
}

View File

@@ -1,6 +1,7 @@
import { getImageProvider } from "@omniroute/open-sse/config/imageRegistry";
import { getProviderOutboundGuard } from "@/shared/network/outboundUrlGuardPolicy";
import { isSecurityBlockError } from "@/lib/providers/validation/transport";
import {
SAFE_OUTBOUND_FETCH_PRESETS,
SafeOutboundFetchError,
@@ -62,7 +63,7 @@ function toValidationErrorResult(error: unknown) {
...(error instanceof SafeOutboundFetchError && error.code === "TIMEOUT"
? { timeout: true }
: {}),
...(statusCode === 400 ? { securityBlocked: true } : {}),
...(isSecurityBlockError(error) ? { securityBlocked: true } : {}),
};
}

View File

@@ -1,4 +1,9 @@
export const DEFAULT_CODEX_CLIENT_VERSION = "0.146.0";
// Kept in lockstep with the codex CLI actually installed in the OmniRoute image
// (bin/omniroute-fix.Containerfile installs `codex` latest; app-server runtime is
// 0.149.0 as of 2026-08-22). When the image's codex is bumped, refresh this so the
// fingerprint OpenAI sees from the OAuth/Responses face matches the real client
// version. Overridable per-deployment via the CODEX_CLIENT_VERSION env.
export const DEFAULT_CODEX_CLIENT_VERSION = "0.149.0";
export const CODEX_CLI_RS_ORIGINATOR = "codex_cli_rs";
export function getCodexCliRsHeaders(

View File

@@ -401,6 +401,18 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [
requiresRestart: false,
warningLevel: "info",
},
{
key: "OMNIROUTE_CODEX_APP_SERVER_ENABLED",
label: "Codex App-Server Transport",
description:
"Allow Codex to use the local app-server WebSocket JSON-RPC transport (codexTransport=app-server). When off, connections opted into app-server fall back to Codex's other transports.",
descriptionI18nKey: "featureFlagOmnirouteCodexAppServerEnabledDescription",
category: "runtime",
defaultValue: "true",
type: "boolean",
requiresRestart: false,
warningLevel: "info",
},
{
key: "OMNIROUTE_EMERGENCY_FALLBACK",
label: "Emergency Fallback",

View File

@@ -175,6 +175,30 @@ export const NOAUTH_PROVIDERS = {
text: "ZCode runs locally through its native app-server. OmniRoute never receives or stores the Z.ai credential.",
},
},
"codex-app-server": {
id: "codex-app-server",
alias: "cxa",
name: "OpenAI Codex (App-Server)",
icon: "code",
color: "#10A37F",
textIcon: "CA",
website: "https://developers.openai.com/codex/cli",
noAuth: true,
hasFree: false,
serviceKinds: ["llm"],
isLocalCli: true,
// No subscriptionRisk / riskNoticeVariant: unlike the `codex` provider (which
// replays your ChatGPT/OpenAI session token to the API), this transport drives
// the Codex CLI's own `codex app-server` over JSON-RPC/WebSocket. The CLI owns
// and self-refreshes its OAuth (~/.codex/auth.json) exactly like an interactive
// `codex` session — OmniRoute never replays a token to the API — so the
// "official session not authorized for proxy use" caveat does not apply.
authHint:
"No token stored by OmniRoute. The Codex CLI app-server manages its own ChatGPT sign-in (~/.codex/auth.json, auto-refreshed). Use \u201cSign in with ChatGPT\u201d if the CLI is not yet authenticated.",
notice: {
text: "OpenAI Codex (App-Server) drives the Codex CLI's local app-server (JSON-RPC over WebSocket). The CLI self-manages its OpenAI OAuth, so OmniRoute never sees or replays your token. Requires the codex CLI reachable at the configured app-server URL; sign in via the CLI or the dashboard \u201cSign in with ChatGPT\u201d action.",
},
},
uncloseai: {
id: "uncloseai",
alias: "unc",

View File

@@ -338,6 +338,7 @@ export const CLI_TOOL_ALIASES: Readonly<Record<string, string>> = {
"claude-code": "claude",
"openai-codex": "codex",
openai: "codex",
"codex-app-server": "codex",
cn: "continue",
qodercli: "qoder",
};

View File

@@ -72,6 +72,8 @@
"tests/unit/antigravity-prefer-stored-project.test.ts",
"tests/unit/api-key-policy-noauth-allowed-connections.test.ts",
"tests/unit/api-key-rotator-health.test.ts",
"tests/unit/chat-routing-synced-inventory-11089.test.ts",
"tests/unit/security-route-guard-tiers.test.ts",
"tests/unit/egress-ip-lock-10880.test.ts",
"tests/unit/egress-lock-allowlist-10880.test.ts",
"tests/unit/api/jobs.test.ts",

View File

@@ -48,10 +48,10 @@ const route = await import("../../src/app/api/search/providers/route.ts");
// Constants
// ---------------------------------------------------------------------------
// 16 search-kind providers: serper, brave, perplexity, exa, tavily, firecrawl,
// 17 search-kind providers: serper, brave, perplexity, exa, tavily, firecrawl,
// google-pse, linkup, searchapi, youcom, searxng, ollama, zai, jina-search,
// duckduckgo-free, x-search (registry open-sse/config/searchRegistry.ts).
const EXPECTED_SEARCH_COUNT = 16;
// context7 (#11140), duckduckgo-free, x-search (registry open-sse/config/searchRegistry.ts).
const EXPECTED_SEARCH_COUNT = 17;
const EXPECTED_FETCH_COUNT = 4;
const EXPECTED_TOTAL = EXPECTED_SEARCH_COUNT + EXPECTED_FETCH_COUNT;
@@ -138,7 +138,7 @@ test("search-providers-catalog: returns 401 for unauthenticated requests when au
assert.ok(!bodyStr.includes(" at /"), "error body must not contain stack trace");
});
test("search-providers-catalog: returns 16 providers (13 search + 3 fetch)", async () => {
test("search-providers-catalog: returns 21 providers (17 search + 4 fetch)", async () => {
const req = await buildAuthRequest();
const res = await route.GET(req);

View File

@@ -17,6 +17,11 @@
"status": 400,
"throws": true
},
"context7": {
"message": "Provider \"context7\" is a search provider and does not support chat completions; use the /v1/search endpoint instead.",
"status": 400,
"throws": true
},
"duckduckgo-free": {
"message": "Provider \"duckduckgo-free\" is a search provider and does not support chat completions; use the /v1/search endpoint instead.",
"status": 400,

View File

@@ -155,6 +155,11 @@
"configSource": "codex",
"provider": "codex"
},
"codex-app-server": {
"className": "CodexAppServerExecutor",
"configSource": "codex-app-server",
"provider": "codex-app-server"
},
"command-code": {
"className": "CommandCodeExecutor",
"configSource": "<custom-config>",
@@ -706,6 +711,6 @@
"provider": "zai-web"
}
},
"keyCount": 141,
"keyCount": 142,
"sharedInstances": []
}

View File

@@ -1311,16 +1311,16 @@
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"Openai-Beta": "responses=experimental",
"User-Agent": "codex-cli/0.146.0 (<OS>; <ARCH>)",
"Version": "0.146.0",
"User-Agent": "codex-cli/0.149.0 (<OS>; <ARCH>)",
"Version": "0.149.0",
"X-Codex-Beta-Features": "responses_websockets"
},
"nonStream": {
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"Openai-Beta": "responses=experimental",
"User-Agent": "codex-cli/0.146.0 (<OS>; <ARCH>)",
"Version": "0.146.0",
"User-Agent": "codex-cli/0.149.0 (<OS>; <ARCH>)",
"Version": "0.149.0",
"X-Codex-Beta-Features": "responses_websockets"
},
"oauth": {
@@ -1328,8 +1328,8 @@
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"Openai-Beta": "responses=experimental",
"User-Agent": "codex-cli/0.146.0 (<OS>; <ARCH>)",
"Version": "0.146.0",
"User-Agent": "codex-cli/0.149.0 (<OS>; <ARCH>)",
"Version": "0.149.0",
"X-Codex-Beta-Features": "responses_websockets"
}
},
@@ -1338,6 +1338,29 @@
"stream": "https://chatgpt.com/backend-api/codex/responses"
}
},
"codex-app-server": {
"format": "openai-responses",
"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": "codex-app-server://cli/websocket",
"stream": "codex-app-server://cli/websocket"
}
},
"cohere": {
"format": "openai",
"headers": {

View File

@@ -0,0 +1,48 @@
// Shared guard for unit tests that construct a real better-sqlite3 Database as a
// test fixture (e.g. seeding a legacy on-disk schema before exercising the
// migration runner). better-sqlite3 is a native addon: production and CI load
// it fine, but some sandboxes/dev boxes ship a system glibc older than the
// prebuilt binary requires (e.g. "GLIBC_2.29 not found"), so `new Database(...)`
// throws ERR_DLOPEN_FAILED at fixture-construction time. That is an environment
// limitation, NOT a defect in the code under test — the OmniRoute runtime itself
// cascades to node:sqlite/sql.js when better-sqlite3 can't load, so the app keeps
// working; only tests that reach for better-sqlite3 DIRECTLY (to build a
// driver-specific fixture) are affected.
//
// Tests import `betterSqlite3Available` to decide whether to run or to skip with
// a clear, documented reason. In CI (where better-sqlite3 loads) the tests run
// normally; only the constrained sandbox skips them.
//
// Usage:
// import { betterSqlite3Available, BETTER_SQLITE3_SKIP_REASON } from "./_helpers/betterSqlite3Availability";
// const canUseBetterSqlite3 = betterSqlite3Available();
// test("...", { skip: canUseBetterSqlite3 ? false : BETTER_SQLITE3_SKIP_REASON }, () => { ... });
import { createRequire } from "node:module";
export const BETTER_SQLITE3_SKIP_REASON =
"better-sqlite3 native addon cannot load in this environment (e.g. system " +
"glibc older than the prebuilt binary requires — 'GLIBC_2.29 not found'). " +
"This is a sandbox/environment limitation, not a code defect: the runtime " +
"cascades to node:sqlite/sql.js, and CI runs this test with a working " +
"better-sqlite3.";
let cached: boolean | null = null;
/**
* Returns true when a real better-sqlite3 Database can be constructed in the
* current environment. Result is memoized. Never throws.
*/
export function betterSqlite3Available(): boolean {
if (cached !== null) return cached;
try {
const require = createRequire(import.meta.url);
const Database = require("better-sqlite3");
const db = new Database(":memory:");
db.close();
cached = true;
} catch {
cached = false;
}
return cached;
}

View File

@@ -112,7 +112,7 @@ test("AgentRouter Responses requests automatically use the native Responses prot
body: structuredClone(body),
headers: new Headers({ accept: "application/json", originator: "codex_cli_rs" }),
},
userAgent: "codex_cli_rs/0.146.0",
userAgent: "codex_cli_rs/0.149.0",
});
assert.ok(captured);
@@ -176,7 +176,7 @@ test("AgentRouter OpenAI Chat requests automatically use the native Chat protoco
body: structuredClone(body),
headers: new Headers({ accept: "application/json" }),
},
userAgent: "codex_cli_rs/0.146.0",
userAgent: "codex_cli_rs/0.149.0",
});
assert.equal(result.success, true);
@@ -304,7 +304,7 @@ test("AgentRouter Responses streaming stays native without a connection protocol
body: structuredClone(body),
headers: new Headers({ accept: "text/event-stream", originator: "codex_cli_rs" }),
},
userAgent: "codex_cli_rs/0.146.0",
userAgent: "codex_cli_rs/0.149.0",
});
assert.equal(result.success, true);
@@ -383,7 +383,7 @@ test("AgentRouter OpenAI Chat streaming stays native without a connection protoc
body: structuredClone(body),
headers: new Headers({ accept: "text/event-stream" }),
},
userAgent: "codex_cli_rs/0.146.0",
userAgent: "codex_cli_rs/0.149.0",
});
assert.equal(result.success, true);

View File

@@ -84,7 +84,7 @@ test("AgentRouter OpenAI Chat dispatch uses Codex identity without Claude-only b
assert.equal(captured.url, "https://agentrouter.org/v1/chat/completions");
assert.equal(captured.headers.get("authorization"), "Bearer test-agentrouter-key");
assert.equal(captured.headers.get("x-api-key"), null);
assert.equal(captured.headers.get("user-agent"), "codex_cli_rs/0.146.0");
assert.equal(captured.headers.get("user-agent"), "codex_cli_rs/0.149.0");
assert.equal(captured.headers.get("originator"), "codex_cli_rs");
assert.equal(captured.headers.get("x-app"), null);
assert.equal(captured.headers.get("anthropic-version"), null);
@@ -130,7 +130,7 @@ test("AgentRouter OpenAI Responses dispatch uses the Responses endpoint and Code
assert.ok(captured);
assert.equal(captured.url, "https://agentrouter.org/v1/responses");
assert.equal(captured.headers.get("authorization"), "Bearer test-agentrouter-key");
assert.equal(captured.headers.get("user-agent"), "codex_cli_rs/0.146.0");
assert.equal(captured.headers.get("user-agent"), "codex_cli_rs/0.149.0");
assert.equal(captured.headers.get("originator"), "codex_cli_rs");
assert.equal(captured.headers.get("x-app"), null);
assert.equal(captured.headers.get("anthropic-beta"), null);

View File

@@ -1,3 +1,14 @@
// ENVIRONMENT NOTE (node:test runner cancellation, not a code defect):
// The subtests below exercise real-timer / AbortSignal.timeout-bounded async
// paths and fire-and-forget work guarded by unref()'d timers. In this sandbox
// they intermittently surface as `cancelledByParent` ("Promise resolution is
// still pending but the event loop has already resolved") rather than pass or
// fail: the node:test runner decides the event loop has settled before the
// unref'd timer/promise chain finishes. This is a pre-existing test-harness /
// runtime interaction (present on the clean tree before the codex-app-server
// work, and unrelated to it) — the code under test resolves correctly when
// invoked directly (e.g. testOAuthConnection(github, 50) returns a bounded
// "timed out" failure in ~50ms). CI, on its runner, completes these normally.
// Regression guard for the Antigravity OAuth login hang.
//
// The dashboard login "just spun forever" because postExchange `await`ed the

View File

@@ -1,3 +1,13 @@
// ENVIRONMENT NOTE (sandbox better-sqlite3 / glibc limitation, not a code defect):
// This test constructs or exercises a real better-sqlite3-backed SQLite database.
// better-sqlite3 is a native addon; production and CI load it normally, but some
// sandboxes/dev boxes ship a system glibc older than the prebuilt binary requires
// ("GLIBC_2.29 not found"), so the native module fails to dlopen and any test that
// reaches better-sqlite3 directly (or asserts stdout that the load-failure warning
// would pollute) fails HERE while passing in CI. This is a known environment
// limitation, not a defect in the code under test: the OmniRoute runtime itself
// cascades to node:sqlite/sql.js when better-sqlite3 is unavailable. See
// tests/unit/_helpers/betterSqlite3Availability.ts for a guard helper.
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";

View File

@@ -1,3 +1,13 @@
// ENVIRONMENT NOTE (sandbox better-sqlite3 / glibc limitation, not a code defect):
// This test constructs or exercises a real better-sqlite3-backed SQLite database.
// better-sqlite3 is a native addon; production and CI load it normally, but some
// sandboxes/dev boxes ship a system glibc older than the prebuilt binary requires
// ("GLIBC_2.29 not found"), so the native module fails to dlopen and any test that
// reaches better-sqlite3 directly (or asserts stdout that the load-failure warning
// would pollute) fails HERE while passing in CI. This is a known environment
// limitation, not a defect in the code under test: the OmniRoute runtime itself
// cascades to node:sqlite/sql.js when better-sqlite3 is unavailable. See
// tests/unit/_helpers/betterSqlite3Availability.ts for a guard helper.
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";

View File

@@ -120,4 +120,14 @@ test("every @huggingface/transformers consumer loads it lazily so absent install
/^\s*import\s+(?:[^'"]*?\s+from\s+)?["']@huggingface\/transformers["']/m,
"onnxWorker.ts must not statically import @huggingface/transformers"
);
// Positive anchor (required by source-scanner-guards.test.ts): prove the read
// resolved to the real, non-empty onnxWorker.ts. Without this, renaming or
// gutting the worker would leave the negative guard above passing while
// protecting nothing. The worker loads the optional transformer deps lazily
// via a dynamicImport() helper, so anchor on that stable call.
assert.match(
workerSrc,
/dynamicImport\(["']@huggingface\/transformers["']\)/,
"onnxWorker.ts must load @huggingface/transformers via a deferred dynamicImport()"
);
});

View File

@@ -88,7 +88,15 @@ describe("ccr protocol instruction (#8033)", () => {
const body = makeBody([{ role: "user", content: LARGE_TEXT }]);
const result = ccrEngine.apply(body);
assert.equal(result.compressed, true, "large block should still compress");
// #7746 follow-up: a caller whose tools[] does not advertise
// omniroute_ccr_retrieve can never resolve a content-addressed marker, so
// replacing its text would strand it behind an unresolvable hash. The engine
// therefore now SKIPS entirely for such callers (callerSupportsCcrRetrieve →
// false ⇒ compressed:false), which is a strictly safer outcome than the old
// "compress the block but withhold the instruction" behavior. Either way the
// guarantee this test pins holds: no CCR marker/instruction reaches a caller
// that cannot use it.
assert.equal(result.compressed, false, "no-retrieve-tool caller must not be compressed");
const messages = result.body["messages"] as Array<{ role: string; content: unknown }>;
assert.equal(messages.length, 1, "no system message should be injected");

View File

@@ -57,9 +57,9 @@ test("Claude CLI wire versions match the captured 2.1.220 binary", () => {
assert.equal(hdr.CLAUDE_CLI_BILLING_VERSION, canonical.CLAUDE_CODE_CLIENT_BILLING_VERSION);
});
test("Codex client is pinned to the captured 0.146.0 release", () => {
assert.equal(codexCfg.getCodexClientVersion(), "0.146.0");
assert.equal(codexCfg.getCodexUserAgent(), "codex-cli/0.146.0 (Windows 10.0.26200; x64)");
assert.equal(codexCfg.getCodexDefaultHeaders().Version, "0.146.0");
assert.equal(codexCfg.getCodexCliRsHeaders()["User-Agent"], "codex_cli_rs/0.146.0");
test("Codex client is pinned to the captured 0.149.0 release", () => {
assert.equal(codexCfg.getCodexClientVersion(), "0.149.0");
assert.equal(codexCfg.getCodexUserAgent(), "codex-cli/0.149.0 (Windows 10.0.26200; x64)");
assert.equal(codexCfg.getCodexDefaultHeaders().Version, "0.149.0");
assert.equal(codexCfg.getCodexCliRsHeaders()["User-Agent"], "codex_cli_rs/0.149.0");
});

View File

@@ -1,3 +1,13 @@
// ENVIRONMENT NOTE (sandbox better-sqlite3 / glibc limitation, not a code defect):
// This test constructs or exercises a real better-sqlite3-backed SQLite database.
// better-sqlite3 is a native addon; production and CI load it normally, but some
// sandboxes/dev boxes ship a system glibc older than the prebuilt binary requires
// ("GLIBC_2.29 not found"), so the native module fails to dlopen and any test that
// reaches better-sqlite3 directly (or asserts stdout that the load-failure warning
// would pollute) fails HERE while passing in CI. This is a known environment
// limitation, not a defect in the code under test: the OmniRoute runtime itself
// cascades to node:sqlite/sql.js when better-sqlite3 is unavailable. See
// tests/unit/_helpers/betterSqlite3Availability.ts for a guard helper.
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";

View File

@@ -1,3 +1,13 @@
// ENVIRONMENT NOTE (sandbox better-sqlite3 / glibc limitation, not a code defect):
// This test constructs or exercises a real better-sqlite3-backed SQLite database.
// better-sqlite3 is a native addon; production and CI load it normally, but some
// sandboxes/dev boxes ship a system glibc older than the prebuilt binary requires
// ("GLIBC_2.29 not found"), so the native module fails to dlopen and any test that
// reaches better-sqlite3 directly (or asserts stdout that the load-failure warning
// would pollute) fails HERE while passing in CI. This is a known environment
// limitation, not a defect in the code under test: the OmniRoute runtime itself
// cascades to node:sqlite/sql.js when better-sqlite3 is unavailable. See
// tests/unit/_helpers/betterSqlite3Availability.ts for a guard helper.
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";

View File

@@ -1,3 +1,13 @@
// ENVIRONMENT NOTE (sandbox better-sqlite3 / glibc limitation, not a code defect):
// This test constructs or exercises a real better-sqlite3-backed SQLite database.
// better-sqlite3 is a native addon; production and CI load it normally, but some
// sandboxes/dev boxes ship a system glibc older than the prebuilt binary requires
// ("GLIBC_2.29 not found"), so the native module fails to dlopen and any test that
// reaches better-sqlite3 directly (or asserts stdout that the load-failure warning
// would pollute) fails HERE while passing in CI. This is a known environment
// limitation, not a defect in the code under test: the OmniRoute runtime itself
// cascades to node:sqlite/sql.js when better-sqlite3 is unavailable. See
// tests/unit/_helpers/betterSqlite3Availability.ts for a guard helper.
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";

View File

@@ -2,6 +2,14 @@ import { describe, it, before } from "node:test";
import assert from "node:assert";
import * as toolDetector from "../../../src/lib/cli-helper/tool-detector.ts";
// The Hermes tool detector honors a HERMES_HOME env var (#3628) and only falls
// back to the default ~/.hermes/config.yaml path when it is unset. CI runs with
// HERMES_HOME unset, but this suite can also run inside a Hermes Agent session
// that exports HERMES_HOME, which redirects the detected config path and breaks
// the ".hermes/config.yaml" assertion below. Unset it so the test is hermetic
// and matches CI regardless of the ambient runtime.
delete process.env.HERMES_HOME;
describe("tool-detector", () => {
before(() => {
// Install mock exec implementation for deterministic testing

View File

@@ -53,6 +53,16 @@ test("ServerSupervisor starts Node with IPv4-first DNS", async () => {
const dataDir = mkdtempSync(join(tmpdir(), "omniroute-ipv4-first-"));
const previousDataDir = process.env.DATA_DIR;
process.env.DATA_DIR = dataDir;
// The supervisor reads process.env (not its own `env`) to decide whether an
// explicit --max-old-space-size is already pinned via NODE_OPTIONS, in which
// case it suppresses its own heap flag (envHasExplicitHeapFlag). CI runs with
// no heap flag in NODE_OPTIONS, but this suite can be launched with an ambient
// NODE_OPTIONS=--max-old-space-size=... (e.g. the sandbox exports one), which
// would make the supervisor legitimately drop the flag and fail the assertion
// below. Neutralize it for the duration of this test so the expectation
// matches the CI environment.
const previousNodeOptions = process.env.NODE_OPTIONS;
delete process.env.NODE_OPTIONS;
try {
const moduleUrl = pathToFileURL(
@@ -76,6 +86,8 @@ test("ServerSupervisor starts Node with IPv4-first DNS", async () => {
} finally {
if (previousDataDir === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = previousDataDir;
if (previousNodeOptions === undefined) delete process.env.NODE_OPTIONS;
else process.env.NODE_OPTIONS = previousNodeOptions;
rmSync(dataDir, { recursive: true, force: true });
}
});

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