diff --git a/docs/CODEBASE_DOCUMENTATION.md b/docs/CODEBASE_DOCUMENTATION.md index 1b16d67844..bf8bdf5d31 100644 --- a/docs/CODEBASE_DOCUMENTATION.md +++ b/docs/CODEBASE_DOCUMENTATION.md @@ -1,589 +1,787 @@ -# omniroute โ€” Codebase Documentation +# OmniRoute Codebase Documentation -๐ŸŒ **Languages:** ๐Ÿ‡บ๐Ÿ‡ธ [English](CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ง๐Ÿ‡ท [Portuguรชs (Brasil)](i18n/pt-BR/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ช๐Ÿ‡ธ [Espaรฑol](i18n/es/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ซ๐Ÿ‡ท [Franรงais](i18n/fr/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฎ๐Ÿ‡น [Italiano](i18n/it/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ท๐Ÿ‡บ [ะ ัƒััะบะธะน](i18n/ru/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡จ๐Ÿ‡ณ [ไธญๆ–‡ (็ฎ€ไฝ“)](i18n/zh-CN/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฉ๐Ÿ‡ช [Deutsch](i18n/de/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฎ๐Ÿ‡ณ [เคนเคฟเคจเฅเคฆเฅ€](i18n/in/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡น๐Ÿ‡ญ [เน„เธ—เธข](i18n/th/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡บ๐Ÿ‡ฆ [ะฃะบั€ะฐั—ะฝััŒะบะฐ](i18n/uk-UA/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ธ๐Ÿ‡ฆ [ุงู„ุนุฑุจูŠุฉ](i18n/ar/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฏ๐Ÿ‡ต [ๆ—ฅๆœฌ่ชž](i18n/ja/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ป๐Ÿ‡ณ [Tiแบฟng Viแป‡t](i18n/vi/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ง๐Ÿ‡ฌ [ะ‘ัŠะปะณะฐั€ัะบะธ](i18n/bg/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฉ๐Ÿ‡ฐ [Dansk](i18n/da/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ซ๐Ÿ‡ฎ [Suomi](i18n/fi/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฎ๐Ÿ‡ฑ [ืขื‘ืจื™ืช](i18n/he/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ญ๐Ÿ‡บ [Magyar](i18n/hu/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฎ๐Ÿ‡ฉ [Bahasa Indonesia](i18n/id/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฐ๐Ÿ‡ท [ํ•œ๊ตญ์–ด](i18n/ko/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ฒ๐Ÿ‡พ [Bahasa Melayu](i18n/ms/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ณ๐Ÿ‡ฑ [Nederlands](i18n/nl/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ณ๐Ÿ‡ด [Norsk](i18n/no/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ต๐Ÿ‡น [Portuguรชs (Portugal)](i18n/pt/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ท๐Ÿ‡ด [Romรขnฤƒ](i18n/ro/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ต๐Ÿ‡ฑ [Polski](i18n/pl/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ธ๐Ÿ‡ฐ [Slovenฤina](i18n/sk/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ธ๐Ÿ‡ช [Svenska](i18n/sv/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡ต๐Ÿ‡ญ [Filipino](i18n/phi/CODEBASE_DOCUMENTATION.md) | ๐Ÿ‡จ๐Ÿ‡ฟ [ฤŒeลกtina](i18n/cs/CODEBASE_DOCUMENTATION.md) +> **Version:** v3.8.0 +> **Last updated:** 2026-05-13 +> **Audience:** Engineers contributing to OmniRoute or building integrations on top of it. +> +> For high-level architecture diagrams and the reasoning behind each subsystem, read +> [ARCHITECTURE.md](./ARCHITECTURE.md). For deep dives on individual subsystems +> (Auto Combo, MCP server, A2A server, Skills, Memory, Cloud Agents, Resilience, +> Compression, etc.) see their dedicated files in this `docs/` directory. -> A comprehensive, beginner-friendly guide to the **omniroute** multi-provider AI proxy router. +This file describes **what exists in the repository today** so that a new engineer +can navigate the tree, understand the runtime layering, and know where to add code +without inventing new modules. --- -## 1. What Is omniroute? +## 1. Tech Stack -omniroute is a **proxy router** that sits between AI clients (Claude CLI, Codex, Cursor IDE, etc.) and AI providers (Anthropic, Google, OpenAI, AWS, GitHub, etc.). It solves one big problem: +| Concern | Choice | +| ------------- | ------------------------------------------------------------------------------------------------------------------------ | --- | ------------- | --- | ------------------------------------ | +| Web framework | **Next.js 16** (App Router, standalone output, no global middleware) | +| Language | **TypeScript 5.9+** โ€” target `ES2022`, `module: esnext`, `moduleResolution: bundler`, `strict: false` | +| Runtime | **Node.js** `>=20.20.2 <21 | | >=22.22.2 <23 | | >=24.0.0 <27`(enforced via`engines`) | +| Database | **SQLite** via `better-sqlite3` (singleton, WAL journaling) | +| Desktop | **Electron 41** + `electron-builder` 26.10 (separate workspace at `electron/`) | +| Tests | **Node native test runner** (unit/integration), **Vitest** (MCP, autoCombo, cache), **Playwright** (e2e + protocols-e2e) | +| Build | Next.js standalone via `scripts/build-next-isolated.mjs` | +| Lint/format | ESLint flat config + Prettier (`lint-staged` via Husky pre-commit) | +| Module system | ESM everywhere (`"type": "module"`) | +| Workspaces | npm workspace โ€” `open-sse` is the only sub-workspace | -> **Different AI clients speak different "languages" (API formats), and different AI providers expect different "languages" too.** omniroute translates between them automatically. +Path aliases (`tsconfig.json`): -Think of it like a universal translator at the United Nations โ€” any delegate can speak any language, and the translator converts it for any other delegate. +- `@/*` โ†’ `src/*` +- `@omniroute/open-sse` โ†’ `open-sse/index.ts` +- `@omniroute/open-sse/*` โ†’ `open-sse/*` + +Default HTTP port: **`20128`** (API and dashboard share the same process). Data +directory is `DATA_DIR` env var, defaulting to `~/.omniroute/`. --- -## 2. Architecture Overview - -```mermaid -graph LR - subgraph Clients - A[Claude CLI] - B[Codex] - C[Cursor IDE] - D[OpenAI-compatible] - end - - subgraph omniroute - E[Handler Layer] - F[Translator Layer] - G[Executor Layer] - H[Services Layer] - end - - subgraph Providers - I[Anthropic Claude] - J[Google Gemini] - K[OpenAI / Codex] - L[GitHub Copilot] - M[AWS Kiro] - N[Antigravity] - O[Cursor API] - end - - A --> E - B --> E - C --> E - D --> E - E --> F - F --> G - G --> I - G --> J - G --> K - G --> L - G --> M - G --> N - G --> O - H -.-> E - H -.-> G -``` - -### Core Principle: Hub-and-Spoke Translation - -All format translation passes through **OpenAI format as the hub**: +## 2. Repository Layout ``` -Client Format โ†’ [OpenAI Hub] โ†’ Provider Format (request) -Provider Format โ†’ [OpenAI Hub] โ†’ Client Format (response) -``` - -This means you only need **N translators** (one per format) instead of **Nยฒ** (every pair). - ---- - -## 3. Project Structure - -``` -omniroute/ -โ”œโ”€โ”€ open-sse/ โ† Core proxy library (portable, framework-agnostic) -โ”‚ โ”œโ”€โ”€ index.js โ† Main entry point, exports everything -โ”‚ โ”œโ”€โ”€ config/ โ† Configuration & constants -โ”‚ โ”œโ”€โ”€ executors/ โ† Provider-specific request execution -โ”‚ โ”œโ”€โ”€ handlers/ โ† Request handling orchestration -โ”‚ โ”œโ”€โ”€ services/ โ† Business logic (auth, models, fallback, usage) -โ”‚ โ”œโ”€โ”€ translator/ โ† Format translation engine -โ”‚ โ”‚ โ”œโ”€โ”€ request/ โ† Request translators (8 files) -โ”‚ โ”‚ โ”œโ”€โ”€ response/ โ† Response translators (7 files) -โ”‚ โ”‚ โ””โ”€โ”€ helpers/ โ† Shared translation utilities (6 files) -โ”‚ โ””โ”€โ”€ utils/ โ† Utility functions -โ”œโ”€โ”€ src/ โ† Application layer (Express/Worker runtime) -โ”‚ โ”œโ”€โ”€ app/ โ† Web UI, API routes, middleware -โ”‚ โ”œโ”€โ”€ lib/ โ† Database, auth, and shared library code -โ”‚ โ”œโ”€โ”€ mitm/ โ† Man-in-the-middle proxy utilities -โ”‚ โ”œโ”€โ”€ models/ โ† Database models -โ”‚ โ”œโ”€โ”€ shared/ โ† Shared utilities (wrappers around open-sse) -โ”‚ โ”œโ”€โ”€ sse/ โ† SSE endpoint handlers -โ”‚ โ””โ”€โ”€ store/ โ† State management -โ”œโ”€โ”€ data/ โ† Runtime data (credentials, logs) -โ”‚ โ””โ”€โ”€ provider-credentials.json (external credentials override, gitignored) -โ””โ”€โ”€ tester/ โ† Test utilities +OmniRoute/ +โ”œโ”€โ”€ src/ Next.js application (App Router, libs, domain, server, shared) +โ”œโ”€โ”€ open-sse/ Streaming engine workspace (@omniroute/open-sse) +โ”œโ”€โ”€ electron/ Desktop wrapper (Electron 41 main + preload) +โ”œโ”€โ”€ bin/ CLI entry points (omniroute, reset-password) +โ”œโ”€โ”€ tests/ Unit, integration, e2e, protocols-e2e, translator, security, fixtures +โ”œโ”€โ”€ scripts/ Build, sync, check, migration, and runtime helper scripts +โ”œโ”€โ”€ docs/ Public documentation (this directory) +โ”œโ”€โ”€ public/ Static assets, PWA manifest, service worker +โ”œโ”€โ”€ config/ Runtime config samples +โ”œโ”€โ”€ images/ Marketing/screenshot assets +โ”œโ”€โ”€ _ideia/, _references/, _mono_repo/, _tasks/ Internal scratch / planning (not shipped) +โ”œโ”€โ”€ CLAUDE.md Repo rules for Claude Code +โ”œโ”€โ”€ AGENTS.md Deeper architecture reference for agents +โ”œโ”€โ”€ package.json v3.8.0, workspace root +โ””โ”€โ”€ tsconfig.json Path aliases + core compiler options ``` --- -## 4. Module-by-Module Breakdown +## 3. `src/` โ€” Next.js Application -### 4.1 Config (`open-sse/config/`) - -The **single source of truth** for all provider configuration. - -| File | Purpose | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `constants.ts` | `PROVIDERS` object with base URLs, OAuth credentials (defaults), headers, and default system prompts for every provider. Also defines `HTTP_STATUS`, `ERROR_TYPES`, `COOLDOWN_MS`, `BACKOFF_CONFIG`, and `SKIP_PATTERNS`. | -| `credentialLoader.ts` | Loads external credentials from `data/provider-credentials.json` and merges them over the hardcoded defaults in `PROVIDERS`. Keeps secrets out of source control while maintaining backwards compatibility. | -| `providerModels.ts` | Central model registry: maps provider aliases โ†’ model IDs. Functions like `getModels()`, `getProviderByAlias()`. | -| `codexInstructions.ts` | System instructions injected into Codex requests (editing constraints, sandbox rules, approval policies). | -| `defaultThinkingSignature.ts` | Default "thinking" signatures for Claude and Gemini models. | -| `ollamaModels.ts` | Schema definition for local Ollama models (name, size, family, quantization). | - -#### Credential Loading Flow - -```mermaid -flowchart TD - A["App starts"] --> B["constants.ts defines PROVIDERS\nwith hardcoded defaults"] - B --> C{"data/provider-credentials.json\nexists?"} - C -->|Yes| D["credentialLoader reads JSON"] - C -->|No| E["Use hardcoded defaults"] - D --> F{"For each provider in JSON"} - F --> G{"Provider exists\nin PROVIDERS?"} - G -->|No| H["Log warning, skip"] - G -->|Yes| I{"Value is object?"} - I -->|No| J["Log warning, skip"] - I -->|Yes| K["Merge clientId, clientSecret,\ntokenUrl, authUrl, refreshUrl"] - K --> F - H --> F - J --> F - F -->|Done| L["PROVIDERS ready with\nmerged credentials"] - E --> L ``` +src/ +โ”œโ”€โ”€ app/ App Router pages + API routes +โ”œโ”€โ”€ lib/ Core libraries (DB, auth, OAuth, skills, memory, โ€ฆ) +โ”œโ”€โ”€ domain/ Pure domain layer (policy, fallback, cost, lockout, โ€ฆ) +โ”œโ”€โ”€ server/ Server-only modules (authz, cors, auth) +โ”œโ”€โ”€ shared/ Types, constants, validation, contracts, utils (cross-boundary safe) +โ”œโ”€โ”€ mitm/ Man-in-the-middle proxy helpers for CLI integration +โ”œโ”€โ”€ models/ Local model metadata / aliasing +โ”œโ”€โ”€ sse/ Legacy SSE handlers that still live under src/ (not open-sse/) +โ”œโ”€โ”€ store/ Client-side state stores +โ”œโ”€โ”€ middleware/ Route-level middleware utilities (not Next.js global middleware) +โ”œโ”€โ”€ scripts/ In-tree scripts importable by app code +โ”œโ”€โ”€ types/ Ambient and shared TS types +โ”œโ”€โ”€ i18n/ Locale bundles +โ”œโ”€โ”€ instrumentation.ts Next.js instrumentation hook +โ”œโ”€โ”€ instrumentation-node.ts +โ”œโ”€โ”€ server-init.ts Process-level bootstrap (env, DB, jobs, sync) +โ””โ”€โ”€ proxy.ts Top-level proxy bootstrap helper +``` + +### 3.1 `src/app/` โ€” App Router + +The App Router exposes both the dashboard UI and the public/management HTTP API. +There is **no global middleware** โ€” interception is done per-route. + +Top-level segments under `src/app/`: + +| Path | Purpose | +| ----------------------------------------------------------------------------- | ----------------------------------------- | +| `api/` | All HTTP API routes (see breakdown below) | +| `a2a/` | A2A JSON-RPC 2.0 endpoint (`POST /a2a`) | +| `.well-known/agent.json/` | A2A Agent Card discovery document | +| `(dashboard)/` | Dashboard UI (route group, no URL prefix) | +| `auth/`, `login/`, `forgot-password/`, `callback/` | Auth flows | +| `landing/` | Marketing/landing page | +| `docs/` | Embedded API docs viewer | +| `status/`, `maintenance/`, `offline/` | Operational pages | +| `privacy/`, `terms/` | Legal pages | +| `400/`, `401/`, `403/`, `408/`, `429/`, `500/`, `502/`, `503/` | Static error pages | +| `error.tsx`, `global-error.tsx`, `not-found.tsx`, `forbidden/`, `loading.tsx` | Framework error/loading boundaries | +| `layout.tsx`, `page.tsx`, `globals.css`, `manifest.ts` | Root shell | + +#### 3.1.1 `src/app/(dashboard)/dashboard/` โ€” UI pages + +`agents`, `analytics`, `api-manager`, `audit`, `auto-combo`, `batch`, `cache`, +`changelog`, `cli-tools`, `cloud-agents`, `combos`, `compression`, `context`, +`costs`, `endpoint`, `health`, `limits`, `logs`, `memory`, `onboarding`, +`playground`, `providers`, `search-tools`, `settings`, `skills`, `system`, +`translator`, `usage`, `webhooks`, plus root `page.tsx`, `HomePageClient.tsx`, +`BootstrapBanner.tsx`. + +#### 3.1.2 `src/app/api/` โ€” Top-level API groups + +``` +src/app/api/ +โ”œโ”€โ”€ a2a/{status, tasks} +โ”œโ”€โ”€ acp/ +โ”œโ”€โ”€ admin/ +โ”œโ”€โ”€ analytics/ +โ”œโ”€โ”€ assess/ +โ”œโ”€โ”€ auth/ +โ”œโ”€โ”€ batches/ +โ”œโ”€โ”€ cache/ +โ”œโ”€โ”€ cli-tools/ +โ”œโ”€โ”€ cloud/{codex-responses-ws} +โ”œโ”€โ”€ combos/ +โ”œโ”€โ”€ compliance/ +โ”œโ”€โ”€ compression/ +โ”œโ”€โ”€ context/ +โ”œโ”€โ”€ db/, db-backups/ +โ”œโ”€โ”€ evals/ +โ”œโ”€โ”€ fallback/ +โ”œโ”€โ”€ files/ +โ”œโ”€โ”€ health/ +โ”œโ”€โ”€ init/ +โ”œโ”€โ”€ internal/{concurrency} +โ”œโ”€โ”€ keys/ +โ”œโ”€โ”€ logs/ +โ”œโ”€โ”€ mcp/{audit, sse, status, stream, tools} +โ”œโ”€โ”€ memory/{health, [id]/, route.ts} +โ”œโ”€โ”€ model-combo-mappings/ +โ”œโ”€โ”€ models/ +โ”œโ”€โ”€ monitoring/ +โ”œโ”€โ”€ oauth/ +โ”œโ”€โ”€ openapi/ +โ”œโ”€โ”€ policies/ +โ”œโ”€โ”€ pricing/ +โ”œโ”€โ”€ provider-metrics/, provider-models/, provider-nodes/ +โ”œโ”€โ”€ providers/ +โ”œโ”€โ”€ rate-limit/, rate-limits/ +โ”œโ”€โ”€ resilience/ +โ”œโ”€โ”€ restart/, shutdown/ +โ”œโ”€โ”€ search/ +โ”œโ”€โ”€ sessions/ +โ”œโ”€โ”€ settings/ +โ”œโ”€โ”€ skills/{executions, [id], install, marketplace, route.ts, skillssh} +โ”œโ”€โ”€ storage/ +โ”œโ”€โ”€ sync/, synced-available-models/ +โ”œโ”€โ”€ system/ +โ”œโ”€โ”€ tags/ +โ”œโ”€โ”€ telemetry/ +โ”œโ”€โ”€ token-health/ +โ”œโ”€โ”€ translator/ +โ”œโ”€โ”€ tunnels/ +โ”œโ”€โ”€ upstream-proxy/ +โ”œโ”€โ”€ usage/ +โ”œโ”€โ”€ v1/ OpenAI-compatible public API +โ”œโ”€โ”€ v1beta/ Gemini-style compat +โ”œโ”€โ”€ version-manager/ +โ””โ”€โ”€ webhooks/ +``` + +#### 3.1.3 `src/app/api/v1/` โ€” OpenAI-compatible public API + +``` +v1/ +โ”œโ”€โ”€ accounts/[id]/ account lookup +โ”œโ”€โ”€ agents/tasks/[id]/, agents/tasks/ A2A-flavored task endpoints +โ”œโ”€โ”€ api/ internal API helpers exposed under v1/api +โ”œโ”€โ”€ audio/{speech, transcriptions}/ TTS + STT +โ”œโ”€โ”€ batches/[id]/{cancel}, batches/ OpenAI Batches API +โ”œโ”€โ”€ chat/completions/ Chat Completions (the main endpoint) +โ”œโ”€โ”€ chatgpt-web/ ChatGPT-Web compat +โ”œโ”€โ”€ completions/ Legacy text completions +โ”œโ”€โ”€ embeddings/ Embeddings +โ”œโ”€โ”€ files/[id]/, files/ Files API +โ”œโ”€โ”€ _helpers/ Shared route helpers (no public URL) +โ”œโ”€โ”€ images/{edits, generations}/ Image gen + edit +โ”œโ”€โ”€ issues/ Triage helper endpoints +โ”œโ”€โ”€ management/{proxies}/ Management-scoped routes inside v1 +โ”œโ”€โ”€ messages/{count_tokens}/ Anthropic-style messages compat +โ”œโ”€โ”€ models/ Model listing (`route.ts`, `catalog.ts`) +โ”œโ”€โ”€ moderations/ Moderation +โ”œโ”€โ”€ music/ Music gen +โ”œโ”€โ”€ providers/[provider]/ Per-provider operations +โ”œโ”€โ”€ quotas/{check} Quota probes +โ”œโ”€โ”€ registered-keys/ Registered key admin +โ”œโ”€โ”€ rerank/ Reranking +โ”œโ”€โ”€ responses/[...path]/ OpenAI Responses API (catch-all) +โ”œโ”€โ”€ search/ Web search +โ”œโ”€โ”€ videos/ Video gen +โ”œโ”€โ”€ ws/ WebSocket bridge +โ””โ”€โ”€ route.ts Index handler +``` + +Every route file follows the same pattern: + +``` +Route โ†’ CORS preflight โ†’ Zod body validation โ†’ optional auth + โ†’ API key policy enforcement โ†’ handler delegation (open-sse) +``` + +`v1beta/` is the Gemini-style compat surface (a thin wrapper that translates into +the same `open-sse/handlers/` pipeline). + +### 3.2 `src/lib/` โ€” Core libraries + +Always import data, sync, OAuth, skill, memory, etc. through these modules. The +table groups the actual directories and notable top-level files. + +| Module | Purpose | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `a2a/` | A2A protocol server: `taskManager.ts`, `streaming.ts`, `taskExecution.ts`, `routingLogger.ts`, `skills/` (5 skills: cost analysis, health report, provider discovery, quota management, smart routing) | +| `acp/` | Agent-Control-Protocol: `index.ts`, `manager.ts`, `registry.ts` | +| `api/` | Internal API helpers: `requireManagementAuth.ts`, `requireCliToolsAuth.ts`, `errorResponse.ts` | +| `auth/` | `managementPassword.ts` (password reset / hashing) | +| `batches/` | OpenAI Batches API service (`service.ts`) | +| `catalog/` | OpenRouter catalog sync (`openrouterCatalog.ts`) | +| `cloudAgent/` | Cloud agent registry: `api.ts`, `baseAgent.ts`, `db.ts`, `index.ts`, `registry.ts`, `types.ts`, `agents/{codex, devin, jules}.ts` | +| `combos/` | Combo resolution helpers | +| `compliance/` | Audit + provider audit: `index.ts`, `providerAudit.ts` | +| `config/` | Runtime config glue | +| `db/` | SQLite domain modules (see ยง3.2.1) | +| `display/` | UI/display helpers used by API responses | +| `embeddings/` | Embedding service registry | +| `env/` | Env loading + introspection | +| `evals/` | Eval runtime | +| `guardrails/` | `piiMasker.ts`, `promptInjection.ts`, `visionBridge.ts`, `visionBridgeHelpers.ts`, `registry.ts`, `base.ts` | +| `jobs/` | Background jobs (`autoUpdate.ts`, โ€ฆ) | +| `memory/` | Persistent memory: `store.ts`, `cache.ts`, `retrieval.ts`, `summarization.ts`, `extraction.ts`, `injection.ts`, `qdrant.ts`, `settings.ts`, `verify.ts`, `schemas.ts`, `types.ts` | +| `monitoring/` | `observability.ts` | +| `oauth/` | OAuth providers (14): `antigravity`, `claude`, `cline`, `codex`, `cursor`, `gemini`, `github`, `gitlab-duo`, `kilocode`, `kimi-coding`, `kiro`, `qoder`, `qwen`, `windsurf` plus `services/`, `utils/{pkce, server, banner, codexAuthFile, ui}`, `constants/oauth.ts` | +| `plugins/` | Plugin loader (`index.ts`) | +| `promptCache/` | `prefixAnalyzer.ts`, `index.ts` | +| `providerModels/` | Managed model lifecycle: `modelDiscovery.ts`, `managedModelImport.ts`, `managedAvailableModels.ts`, `cursorAgent.ts` | +| `providers/` | Provider helpers: `catalog.ts`, `validation.ts`, `imageValidation.ts`, `claudeExtraUsage.ts`, `codexConnectionDefaults.ts`, `codexFastTier.ts`, `webCookieAuth.ts`, `managedAvailableModels.ts`, `requestDefaults.ts` | +| `resilience/` | `settings.ts` โ€” settings for circuit breaker, cooldown, lockout | +| `runtime/` | Runtime feature detection | +| `search/` | `executeWebSearch.ts` | +| `skills/` | Skill framework: `registry.ts`, `executor.ts`, `interception.ts`, `injection.ts`, `sandbox.ts`, `custom.ts`, `hybrid.ts`, `builtins.ts`, `a2a.ts`, `providerSettings.ts`, `schemas.ts`, `skillssh.ts`, `types.ts`, plus `builtin/browser.ts` | +| `spend/` | `batchWriter.ts` (write-behind buffer) | +| `sync/` | `bundle.ts`, `tokens.ts` (Cloud Sync) | +| `system/` | System-level helpers | +| `translator/` | Top-level translator glue (delegates into `open-sse/translator/`) | +| `usage/` | Usage accounting: `costCalculator.ts`, `tokenAccounting.ts`, `usageHistory.ts`, `aggregateHistory.ts`, `usageStats.ts`, `callLogs.ts`, `callLogArtifacts.ts`, `fetcher.ts`, `providerLimits.ts`, `migrations.ts` | +| `versionManager/` | Auto-update + version manifest | +| `ws/` | WebSocket bridge | +| `zed-oauth/` | Zed editor OAuth flow | + +Top-level files in `src/lib/`: + +- `localDb.ts` โ€” re-export layer only. **Never** add logic here. +- `proxyHealth.ts`, `proxyLogger.ts`, `tokenHealthCheck.ts`, `localHealthCheck.ts` +- `oneproxyRotator.ts`, `oneproxySync.ts` +- `apiBridgeServer.ts`, `cacheLayer.ts`, `semanticCache.ts`, `settingsCache.ts` +- `cloudSync.ts`, `initCloudSync.ts` +- `cloudflaredTunnel.ts`, `ngrokTunnel.ts`, `tailscaleTunnel.ts` +- `consoleInterceptor.ts`, `container.ts`, `gracefulShutdown.ts`, `idempotencyLayer.ts` +- `ipUtils.ts`, `logEnv.ts`, `logPayloads.ts`, `logRotation.ts` +- `modelAliasSeed.ts`, `modelCapabilities.ts`, `modelMetadataRegistry.ts`, `modelsDevSync.ts` +- `piiSanitizer.ts`, `pricingSync.ts` +- `apiKeyExposure.ts`, `cacheControlSettings.ts`, `dataPaths.ts`, `toolPolicy.ts` +- `translatorEvents.ts`, `usageDb.ts`, `usageAnalytics.ts`, `webhookDispatcher.ts` + +#### 3.2.1 `src/lib/db/` + +Singleton SQLite database (`getDbInstance()` in `core.ts`, WAL journaling). +**Never write raw SQL in routes or handlers** โ€” go through these modules. + +Domain modules (each owns one or more tables): `apiKeys.ts`, `backup.ts`, +`batches.ts`, `cleanup.ts`, `cliToolState.ts`, `combos.ts`, +`commandCodeAuth.ts`, `compression.ts`, `compressionAnalytics.ts`, +`compressionCacheStats.ts`, `compressionCombos.ts`, `compressionScheduler.ts`, +`contextHandoffs.ts`, `core.ts`, `creditBalance.ts`, `databaseSettings.ts`, +`detailedLogs.ts`, `domainState.ts`, `encryption.ts`, `evals.ts`, `files.ts`, +`healthCheck.ts`, `jsonMigration.ts`, `migrationRunner.ts`, +`modelComboMappings.ts`, `models.ts`, `oneproxy.ts`, `prompts.ts`, +`providers.ts`, `providerLimits.ts`, `proxies.ts`, `quotaSnapshots.ts`, +`readCache.ts`, `reasoningCache.ts`, `registeredKeys.ts`, `secrets.ts`, +`sessionAccountAffinity.ts`, `settings.ts`, `stateReset.ts`, `stats.ts`, +`syncTokens.ts`, `tierConfig.ts`, `upstreamProxy.ts`, `versionManager.ts`, +`webhooks.ts`. + +`migrations/` holds 55 versioned `.sql` files (idempotent, transactional) and is +executed by `migrationRunner.ts` at boot. + +Tables created across the migrations (52 total): + +`a`, `account_key_limits`, `api_keys`, `batches`, `call_logs`, +`combo_adaptation_state`, `combos`, `command_code_auth_sessions`, +`compression_analytics`, `compression_cache_stats`, +`compression_combo_assignments`, `compression_combos`, `context_handoffs`, +`daily_usage_summary`, `db_meta`, `domain_budgets`, `domain_circuit_breakers`, +`domain_cost_history`, `domain_fallback_chains`, `domain_lockout_state`, +`eval_cases`, `eval_runs`, `eval_suites`, `files`, `hourly_usage_summary`, +`key_value`, `mcp_tool_audit`, `memories`, `model_combo_mappings`, +`provider_connections`, `provider_key_limits`, `provider_nodes`, +`proxy_assignments`, `proxy_logs`, `proxy_registry`, `quota_snapshots`, +`reasoning_cache`, `registered_keys`, `request_detail_logs`, +`routing_decisions`, `semantic_cache`, `session_account_affinity`, +`skill_executions`, `skills`, `sync_tokens`, `tier_assignments`, +`tier_config`, `upstream_proxy_config`, `usage_history`, `version_manager`, +`webhooks` (plus FTS5 virtual tables for memory search). + +### 3.3 `src/domain/` โ€” Domain layer + +Pure business logic, no I/O. Imported by routes and handlers. + +| File | Purpose | +| ------------------------------------------ | ------------------------------------------------- | +| `policyEngine.ts` | Top-level policy resolver | +| `fallbackPolicy.ts` | Fallback decision tree | +| `costRules.ts` | Cost calculation rules | +| `lockoutPolicy.ts` | Model lockout decisions | +| `tagRouter.ts` | Tag-based routing | +| `comboResolver.ts` | Combo resolution from request โ†’ target list | +| `connectionModelRules.ts` | Per-connection model filters | +| `modelAvailability.ts` | Model availability check | +| `degradation.ts` | Degraded-mode transitions | +| `providerExpiration.ts` | Expired account/key detection | +| `quotaCache.ts` | Cached quota decisions | +| `responses.ts`, `omnirouteResponseMeta.ts` | Response shape helpers | +| `configAudit.ts` | Config change audit | +| `assessment/` | Model assessment (per RFC, partially implemented) | +| `types.ts` | Shared domain types | + +### 3.4 `src/server/` โ€” Server-only + +Cannot be imported from client components. + +``` +server/ +โ”œโ”€โ”€ auth/loginGuard.ts +โ”œโ”€โ”€ authz/ +โ”‚ โ”œโ”€โ”€ classify.ts Classifies routes as public vs management +โ”‚ โ”œโ”€โ”€ assertAuth.ts Assertion helper +โ”‚ โ”œโ”€โ”€ context.ts Per-request authz context +โ”‚ โ”œโ”€โ”€ headers.ts +โ”‚ โ”œโ”€โ”€ pipeline.ts Authz pipeline +โ”‚ โ”œโ”€โ”€ policies/ Concrete policies +โ”‚ โ””โ”€โ”€ types.ts +โ””โ”€โ”€ cors/origins.ts CORS origin allowlist +``` + +### 3.5 `src/shared/` โ€” Safe-to-share + +Split into focused subdirectories: + +- `constants/` โ€” `providers.ts` (Zod-validated provider catalog), `models.ts`, + `modelSpecs.ts`, `modelCompat.ts`, `pricing.ts`, `cliTools.ts`, + `cliCompatProviders.ts`, `routingStrategies.ts`, `comboConfigMode.ts`, + `headers.ts`, `upstreamHeaders.ts` (denylist), `mcpScopes.ts`, + `errorCodes.ts`, `publicApiRoutes.ts`, `batch.ts`, `batchEndpoints.ts`, + `bodySize.ts`, `colors.ts`, `appConfig.ts`, `config.ts`, + `sidebarVisibility.ts`, `visionBridgeDefaults.ts`. +- `validation/` โ€” `schemas.ts` (~80 Zod schemas), `compressionConfigSchemas.ts`, + `oneproxySchemas.ts`, `providerSchema.ts`, `settingsSchemas.ts`, `helpers.ts`. +- `contracts/` โ€” public API contracts shipped to npm. +- `types/` โ€” shared TS types. +- `utils/` โ€” `circuitBreaker.ts`, `apiAuth.ts`, `apiKey.ts`, `apiKeyPolicy.ts`, + `apiResponse.ts`, `api.ts`, `classify429.ts`, `cliCompat.ts`, `clipboard.ts`, + `cloud.ts`, `cn.ts`, `cors.ts`, `costEstimator.ts`, `featureFlags.ts`, + `fetchTimeout.ts`, `formatting.ts`, `inputSanitizer.ts`, `logger.ts`, + `machine.ts`, `machineId.ts`, `maskEmail.ts`, `modelCatalogSearch.ts`, + `nodeRuntimeSupport.ts`, `parseApiKeys.ts`, `providerHints.ts`, + `providerModelAliases.ts`, `rateLimiter.ts`, `releaseNotes.ts`, + `a11yAudit.ts`, plus dashboard hooks/components under `services/`, `network/`, + `middleware/`, `schemas/`, `hooks/`, `components/`. --- -### 4.2 Executors (`open-sse/executors/`) +## 4. `open-sse/` โ€” Streaming engine workspace -Executors encapsulate **provider-specific logic** using the **Strategy Pattern**. Each executor overrides base methods as needed. +Separate npm workspace published as `@omniroute/open-sse`. Owns request +processing, executors, translators, services, transformer, and the MCP server. -```mermaid -classDiagram - class BaseExecutor { - +buildUrl(model, stream, options) - +buildHeaders(credentials, stream, body) - +transformRequest(body, model, stream, credentials) - +execute(url, options) - +shouldRetry(status, error) - +refreshCredentials(credentials, log) - } - - class DefaultExecutor { - +refreshCredentials() - } - - class AntigravityExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +shouldRetry() - +refreshCredentials() - } - - class CursorExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +parseResponse() - +generateChecksum() - } - - class KiroExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +parseEventStream() - +refreshCredentials() - } - - BaseExecutor <|-- DefaultExecutor - BaseExecutor <|-- AntigravityExecutor - BaseExecutor <|-- CursorExecutor - BaseExecutor <|-- KiroExecutor - BaseExecutor <|-- CodexExecutor - BaseExecutor <|-- GeminiCLIExecutor - BaseExecutor <|-- GithubExecutor +``` +open-sse/ +โ”œโ”€โ”€ index.ts Public exports +โ”œโ”€โ”€ package.json Workspace manifest +โ”œโ”€โ”€ tsconfig.json +โ”œโ”€โ”€ types.d.ts +โ”œโ”€โ”€ config/ Provider registries, header profiles, identity, โ€ฆ +โ”œโ”€โ”€ handlers/ Request handlers (chat, embeddings, audio, image, โ€ฆ) +โ”œโ”€โ”€ executors/ 31 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, โ€ฆ) +โ”œโ”€โ”€ utils/ Streaming helpers, TLS client, AWS SigV4, proxy fetch, โ€ฆ +โ””โ”€โ”€ mcp-server/ MCP server (3 transports, 13 scopes, 42 tools) ``` -| Executor | Provider | Key Specializations | -| ---------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | -| `base.ts` | โ€” | Abstract base: URL building, headers, retry logic, credential refresh | -| `default.ts` | Claude, Gemini, OpenAI, GLM, Kimi, MiniMax | Generic OAuth token refresh for standard providers | -| `antigravity.ts` | Google Cloud Code | Project/session ID generation, multi-URL fallback, custom retry parsing from error messages ("reset after 2h7m23s") | -| `cursor.ts` | Cursor IDE | **Most complex**: SHA-256 checksum auth, Protobuf request encoding, binary EventStream โ†’ SSE response parsing | -| `codex.ts` | OpenAI Codex | Injects system instructions, manages thinking levels, removes unsupported parameters | -| `gemini-cli.ts` | Google Gemini CLI | Custom URL building (`streamGenerateContent`), Google OAuth token refresh | -| `github.ts` | GitHub Copilot | Dual token system (GitHub OAuth + Copilot token), VSCode header mimicking | -| `kiro.ts` | AWS CodeWhisperer | AWS EventStream binary parsing, AMZN event frames, token estimation | -| `index.ts` | โ€” | Factory: maps provider name โ†’ executor class, with default fallback | +### 4.1 `open-sse/handlers/` + +| Handler | Purpose | +| ----------------------- | ------------------------------------------------------------------------ | +| `chatCore.ts` | Main chat pipeline (cache, rate limit, combo routing, executor dispatch) | +| `responsesHandler.ts` | OpenAI Responses API entry point | +| `embeddings.ts` | Embeddings | +| `imageGeneration.ts` | Image generation | +| `audioSpeech.ts` | Text-to-speech | +| `audioTranscription.ts` | Speech-to-text | +| `videoGeneration.ts` | Video generation | +| `musicGeneration.ts` | Music generation | +| `rerank.ts` | Reranking | +| `moderations.ts` | Moderation | +| `search.ts` | Web search | +| `sseParser.ts` | SSE event parser | +| `usageExtractor.ts` | Pull token counts out of upstream streams | +| `responseSanitizer.ts` | Strip provider-specific noise | +| `responseTranslator.ts` | Glue between provider response and translator layer | + +### 4.2 `open-sse/executors/` + +31 provider executors, each extending `BaseExecutor` (`base.ts`): + +`antigravity`, `azure-openai`, `blackbox-web`, `chatgpt-web`, `cliproxyapi`, +`cloudflare-ai`, `codex`, `commandCode`, `cursor`, `default`, `devin-cli`, +`gemini-cli`, `github`, `gitlab`, `glm`, `grok-web`, `kie`, `kiro`, +`muse-spark-web`, `nlpcloud`, `opencode`, `perplexity-web`, `petals`, +`pollinations`, `puter`, `qoder`, `vertex`, `windsurf`, plus `claudeIdentity.ts` +(shared identity helper) and `index.ts` (registry). + +> Note: providers not listed here are served by `default.ts` using the generic +> OpenAI-compatible executor. The full provider catalog (179+ entries) lives in +> `src/shared/constants/providers.ts`. + +### 4.3 `open-sse/translator/` + +Hub-and-spoke translation (OpenAI is the hub). + +- **9 request translators** (`translator/request/`): + `antigravity-to-openai`, `claude-to-gemini`, `claude-to-openai`, + `gemini-to-openai`, `openai-responses`, `openai-to-claude`, + `openai-to-cursor`, `openai-to-gemini`, `openai-to-kiro`. +- **8 response translators** (`translator/response/`): + `claude-to-openai`, `cursor-to-openai`, `gemini-to-claude`, `gemini-to-openai`, + `kiro-to-openai`, `openai-responses`, `openai-to-antigravity`, + `openai-to-claude`. +- **9 helpers** (`translator/helpers/`): + `claudeHelper`, `geminiHelper`, `geminiToolsSanitizer`, `maxTokensHelper`, + `openaiHelper`, `responsesApiHelper`, `schemaCoercion`, `toolCallHelper`, plus + helper tests. +- **Image helpers** (`translator/image/sizeMapper.ts`). +- Top-level: `bootstrap.ts`, `formats.ts`, `registry.ts`, `index.ts`. + +### 4.4 `open-sse/transformer/` + +- `responsesTransformer.ts` โ€” `TransformStream`-based Responses API โ†” Chat + Completions converter (used by the `responses/` route catch-all). + +### 4.5 `open-sse/services/` + +Highlights (full list under `open-sse/services/`): + +| Concern | Files | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Combo routing | `combo.ts` (14 strategies), `comboConfig.ts`, `comboMetrics.ts`, `comboManifestMetrics.ts`, `comboAgentMiddleware.ts` | +| Auto Combo engine | `autoCombo/` โ€” `engine.ts`, `scoring.ts`, `taskFitness.ts`, `virtualFactory.ts`, `modePacks.ts`, `autoPrefix.ts`, `persistence.ts`, `providerDiversity.ts`, `providerRegistryAccessor.ts`, `routerStrategy.ts`, `selfHealing.ts`, `index.ts` | +| Resilience | `accountFallback.ts` (cooldown + lockout), `errorClassifier.ts`, `emergencyFallback.ts`, `rateLimitManager.ts`, `rateLimitSemaphore.ts`, `accountSemaphore.ts`, `accountSelector.ts` | +| Quotas | `quotaMonitor.ts`, `quotaPreflight.ts`, `bailianQuotaFetcher.ts`, `codexQuotaFetcher.ts`, `deepseekQuotaFetcher.ts`, `crofUsageFetcher.ts`, `antigravityCredits.ts` | +| Provider-specific shaping | `claudeCodeCCH.ts`, `claudeCodeCompatible.ts`, `claudeCodeConstraints.ts`, `claudeCodeExtraRemap.ts`, `claudeCodeFingerprint.ts`, `claudeCodeObfuscation.ts`, `claudeCodeToolRemapper.ts`, `cloudCodeHeaders.ts`, `cloudCodeThinking.ts`, `geminiCliHeaders.ts`, `geminiThoughtSignatureStore.ts`, `gigachatAuth.ts`, `antigravityHeaders.ts`, `antigravityHeaderScrub.ts`, `antigravityIdentity.ts`, `antigravityObfuscation.ts`, `antigravityVersion.ts`, `antigravity429Engine.ts`, `chatgptTlsClient.ts`, `chatgptImageCache.ts`, `cursorSessionManager.ts`, `qoderCli.ts`, `qwenThinking.ts`, `modelscopePolicy.ts` | +| Caching | `reasoningCache.ts`, `searchCache.ts`, `signatureCache.ts`, `requestDedup.ts` | +| Routing intelligence | `intentClassifier.ts`, `taskAwareRouter.ts`, `backgroundTaskDetector.ts`, `volumeDetector.ts`, `wildcardRouter.ts`, `workflowFSM.ts`, `specificityDetector.ts`, `specificityRules.ts`, `specificityTypes.ts` | +| Model handling | `modelCapabilities.ts`, `modelDeprecation.ts`, `modelFamilyFallback.ts`, `modelStrip.ts`, `model.ts`, `provider.ts`, `providerRequestDefaults.ts`, `providerCostData.ts`, `payloadRules.ts` | +| Compression | `compression/` โ€” full compression engine wiring | +| Token + session | `tokenRefresh.ts`, `sessionManager.ts`, `apiKeyRotator.ts`, `contextManager.ts`, `contextHandoff.ts`, `systemPrompt.ts`, `roleNormalizer.ts`, `responsesInputSanitizer.ts`, `responsesToolCallState.ts`, `toolSchemaSanitizer.ts`, `toolLimitDetector.ts`, `thinkingBudget.ts` | +| Tier / manifest | `tierResolver.ts`, `tierConfig.ts`, `tierDefaults.json`, `tierTypes.ts`, `manifestAdapter.ts` | +| IP / network | `ipFilter.ts`, `webSearchFallback.ts` | +| Batches | `batchProcessor.ts` | +| Usage | `usage.ts` | + +### 4.6 `open-sse/mcp-server/` + +- **31 registered tools** wired in `server.ts` (12 scoped under `schemas/tools.ts`, + 5 compression tools, 3 memory tools, 4 skills tools, plus advanced tools added + through `advancedTools.ts`). +- **3 transports**: stdio, HTTP Streamable, SSE. +- **13 scopes** declared in `src/shared/constants/mcpScopes.ts`. +- Audit table: `mcp_tool_audit` (populated by `audit.ts`). +- Files: `server.ts`, `index.ts`, `httpTransport.ts`, `audit.ts`, `scopeEnforcement.ts`, + `runtimeHeartbeat.ts`, `descriptionCompressor.ts`, `schemas/{tools, a2a, audit, index}.ts`, + `tools/{advancedTools, compressionTools, memoryTools, skillTools}.ts`, + plus tests under `__tests__/`. +- See [MCP-SERVER.md](./MCP-SERVER.md) for the full tool catalog. + +### 4.7 `open-sse/config/` + +Provider registries (`providerRegistry.ts`, `providerModels.ts`, +`providerHeaderProfiles.ts`), per-format model registries (`audioRegistry.ts`, +`embeddingRegistry.ts`, `imageRegistry.ts`, `moderationRegistry.ts`, +`musicRegistry.ts`, `rerankRegistry.ts`, `searchRegistry.ts`, `videoRegistry.ts`), +identity helpers (`codexIdentity.ts`, `codexInstructions.ts`, +`anthropicHeaders.ts`, `antigravityUpstream.ts`, `antigravityModelAliases.ts`, +`cliFingerprints.ts`, `toolCloaking.ts`, `defaultThinkingSignature.ts`), +credential helpers (`credentialLoader.ts`, `codexClient.ts`), and cloud +adapters (`azureAi.ts`, `bedrock.ts`, `datarobot.ts`, `glmProvider.ts`, +`maritalk.ts`, `oci.ts`, `petals.ts`, `runway.ts`, `sap.ts`, `watsonx.ts`, +`ollamaModels.ts`, `errorConfig.ts`, `constants.ts`, `registryUtils.ts`). + +### 4.8 `open-sse/utils/` + +Streaming primitives and provider helpers: `stream.ts`, `streamHandler.ts`, +`streamHelpers.ts`, `streamPayloadCollector.ts`, `streamReadiness.ts`, +`sseHeartbeat.ts`, `proxyFetch.ts`, `proxyDispatcher.ts`, `tlsClient.ts`, +`networkProxy.ts`, `awsSigV4.ts`, `cacheControlPolicy.ts`, +`cursorChecksum.ts`, `cursorAgentProtobuf.ts`, `cursorVersionDetector.ts`, +`comfyuiClient.ts`, `kieTask.ts`, `bypassHandler.ts`, `aiSdkCompat.ts`, +`thinkTagParser.ts`, `urlSanitize.ts`, `usageTracking.ts`, `requestLogger.ts`, +`progressTracker.ts`, `cors.ts`, `error.ts`, `logger.ts`, `sleep.ts`, +`ollamaTransform.ts`. --- -### 4.3 Handlers (`open-sse/handlers/`) +## 5. `electron/` โ€” Desktop wrapper -The **orchestration layer** โ€” coordinates translation, execution, streaming, and error handling. - -| File | Purpose | -| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `chatCore.ts` | **Central orchestrator** (~600 lines). Handles the complete request lifecycle: format detection โ†’ translation โ†’ executor dispatch โ†’ streaming/non-streaming response โ†’ token refresh โ†’ error handling โ†’ usage logging. | -| `responsesHandler.ts` | Adapter for OpenAI's Responses API: converts Responses format โ†’ Chat Completions โ†’ sends to `chatCore` โ†’ converts SSE back to Responses format. | -| `embeddings.ts` | Embedding generation handler: resolves embedding model โ†’ provider, dispatches to provider API, returns OpenAI-compatible embedding response. Supports 6+ providers. | -| `imageGeneration.ts` | Image generation handler: resolves image model โ†’ provider, supports OpenAI-compatible, Gemini-image (Antigravity), and fallback (Nebius) modes. Returns base64 or URL images. | - -#### Request Lifecycle (chatCore.ts) - -```mermaid -sequenceDiagram - participant Client - participant chatCore - participant Translator - participant Executor - participant Provider - - Client->>chatCore: Request (any format) - chatCore->>chatCore: Detect source format - chatCore->>chatCore: Check bypass patterns - chatCore->>chatCore: Resolve model & provider - chatCore->>Translator: Translate request (source โ†’ OpenAI โ†’ target) - chatCore->>Executor: Get executor for provider - Executor->>Executor: Build URL, headers, transform request - Executor->>Executor: Refresh credentials if needed - Executor->>Provider: HTTP fetch (streaming or non-streaming) - - alt Streaming - Provider-->>chatCore: SSE stream - chatCore->>chatCore: Pipe through SSE transform stream - Note over chatCore: Transform stream translates
each chunk: target โ†’ OpenAI โ†’ source - chatCore-->>Client: Translated SSE stream - else Non-streaming - Provider-->>chatCore: JSON response - chatCore->>Translator: Translate response - chatCore-->>Client: Translated JSON - end - - alt Error (401, 429, 500...) - chatCore->>Executor: Retry with credential refresh - chatCore->>chatCore: Account fallback logic - end ``` +electron/ +โ”œโ”€โ”€ main.js Electron main process +โ”œโ”€โ”€ preload.js Preload bridge (contextIsolation enabled) +โ”œโ”€โ”€ types.d.ts +โ”œโ”€โ”€ package.json electron-builder config, version 3.8.0 +โ”œโ”€โ”€ README.md +โ”œโ”€โ”€ assets/ Build resources (icons, entitlements, โ€ฆ) +โ”œโ”€โ”€ node_modules/ Dedicated node_modules (better-sqlite3, electron-updater) +โ””โ”€โ”€ dist-electron/ Build output (not committed) +``` + +Five npm scripts at the workspace root: `electron:dev`, `electron:build`, +`electron:build:{win,mac,linux}`, `electron:smoke:packaged`. Auto-update is via +`electron-updater` pointing at the GitHub release feed. --- -### 4.4 Services (`open-sse/services/`) +## 6. `bin/` โ€” CLI -Business logic that supports the handlers and executors. - -| File | Purpose | -| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `provider.ts` | **Format detection** (`detectFormat`): analyzes request body structure to identify Claude/OpenAI/Gemini/Antigravity/Responses formats (includes `max_tokens` heuristic for Claude). Also: URL building, header building, thinking config normalization. Supports `openai-compatible-*` and `anthropic-compatible-*` dynamic providers. | -| `model.ts` | Model string parsing (`claude/model-name` โ†’ `{provider: "claude", model: "model-name"}`), alias resolution with collision detection, input sanitization (rejects path traversal/control chars), and model info resolution with async alias getter support. | -| `accountFallback.ts` | Rate-limit handling: exponential backoff (1s โ†’ 2s โ†’ 4s โ†’ max 2min), account cooldown management, error classification (which errors trigger fallback vs. not). | -| `tokenRefresh.ts` | OAuth token refresh for **every provider**: Google (Gemini, Antigravity), Claude, Codex, Qwen, Qoder, GitHub (OAuth + Copilot dual-token), Kiro (AWS SSO OIDC + Social Auth). Includes in-flight promise deduplication cache and retry with exponential backoff. | -| `combo.ts` | **Combo models**: chains of fallback models. If model A fails with a fallback-eligible error, try model B, then C, etc. Returns actual upstream status codes. | -| `usage.ts` | Fetches quota/usage data from provider APIs (GitHub Copilot quotas, Antigravity model quotas, Codex rate limits, Kiro usage breakdowns, Claude settings). | -| `accountSelector.ts` | Smart account selection with scoring algorithm: considers priority, health status, round-robin position, and cooldown state to pick the optimal account for each request. | -| `contextManager.ts` | Request context lifecycle management: creates and tracks per-request context objects with metadata (request ID, timestamps, provider info) for debugging and logging. | -| `ipFilter.ts` | IP-based access control: supports allowlist and blocklist modes. Validates client IP against configured rules before processing API requests. | -| `sessionManager.ts` | Session tracking with client fingerprinting: tracks active sessions using hashed client identifiers, monitors request counts, and provides session metrics. | -| `signatureCache.ts` | Request signature-based deduplication cache: prevents duplicate requests by caching recent request signatures and returning cached responses for identical requests within a time window. | -| `systemPrompt.ts` | Global system prompt injection: prepends or appends a configurable system prompt to all requests, with per-provider compatibility handling. | -| `thinkingBudget.ts` | Reasoning token budget management: supports passthrough, auto (strip thinking config), custom (fixed budget), and adaptive (complexity-scaled) modes for controlling thinking/reasoning tokens. | -| `wildcardRouter.ts` | Wildcard model pattern routing: resolves wildcard patterns (e.g., `*/claude-*`) to concrete provider/model pairs based on availability and priority. | - -#### Token Refresh Deduplication - -```mermaid -sequenceDiagram - participant R1 as Request 1 - participant R2 as Request 2 - participant Cache as refreshPromiseCache - participant OAuth as OAuth Provider - - R1->>Cache: getAccessToken("gemini", token) - Cache->>Cache: No in-flight promise - Cache->>OAuth: Start refresh - R2->>Cache: getAccessToken("gemini", token) - Cache->>Cache: Found in-flight promise - Cache-->>R2: Return existing promise - OAuth-->>Cache: New access token - Cache-->>R1: New access token - Cache-->>R2: Same access token (shared) - Cache->>Cache: Delete cache entry +``` +bin/ +โ”œโ”€โ”€ omniroute.mjs Main CLI entry (Node ESM) +โ”œโ”€โ”€ reset-password.mjs Reset the management password from CLI +โ”œโ”€โ”€ mcp-server.mjs MCP server launcher (stdio) +โ”œโ”€โ”€ cli-commands.mjs Command dispatcher +โ”œโ”€โ”€ nodeRuntimeSupport.mjs Node version guard +โ””โ”€โ”€ cli/ + โ”œโ”€โ”€ index.mjs + โ”œโ”€โ”€ args.mjs + โ”œโ”€โ”€ data-dir.mjs + โ”œโ”€โ”€ encryption.mjs + โ”œโ”€โ”€ io.mjs + โ”œโ”€โ”€ provider-catalog.mjs + โ”œโ”€โ”€ provider-store.mjs + โ”œโ”€โ”€ provider-test.mjs + โ”œโ”€โ”€ settings-store.mjs + โ”œโ”€โ”€ sqlite.mjs + โ””โ”€โ”€ commands/ + โ”œโ”€โ”€ setup.mjs + โ”œโ”€โ”€ doctor.mjs + โ””โ”€โ”€ providers.mjs ``` -#### Account Fallback State Machine +Two binaries are exposed in `package.json` โ†’ `bin`: -```mermaid -stateDiagram-v2 - [*] --> Active - Active --> Error: Request fails (401/429/500) - Error --> Cooldown: Apply backoff - Cooldown --> Active: Cooldown expires - Active --> Active: Request succeeds (reset backoff) - - state Error { - [*] --> ClassifyError - ClassifyError --> ShouldFallback: Rate limit / Auth / Transient - ClassifyError --> NoFallback: 400 Bad Request - } - - state Cooldown { - [*] --> ExponentialBackoff - ExponentialBackoff: Level 0 = 1s - ExponentialBackoff: Level 1 = 2s - ExponentialBackoff: Level 2 = 4s - ExponentialBackoff: Max = 2min - } -``` - -#### Combo Model Chain - -```mermaid -flowchart LR - A["Request with\ncombo model"] --> B["Model A"] - B -->|"2xx Success"| C["Return response"] - B -->|"429/401/500"| D{"Fallback\neligible?"} - D -->|Yes| E["Model B"] - D -->|No| F["Return error"] - E -->|"2xx Success"| C - E -->|"429/401/500"| G{"Fallback\neligible?"} - G -->|Yes| H["Model C"] - G -->|No| F - H -->|"2xx Success"| C - H -->|"Fail"| I["All failed โ†’\nReturn last status"] -``` +- `omniroute` โ†’ `bin/omniroute.mjs` +- `omniroute-reset-password` โ†’ `bin/reset-password.mjs` --- -### 4.5 Translator (`open-sse/translator/`) +## 7. `tests/` -The **format translation engine** using a self-registering plugin system. +| Directory | Type | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | +| `tests/unit/` | Unit tests via Node native test runner (506 files, plus `api/`, `auth/`, `authz/` subdirs) | +| `tests/integration/` | Cross-module + DB-state tests | +| `tests/e2e/` | Playwright UI tests | +| `tests/protocols-e2e/` | MCP/A2A protocol e2e | +| `tests/translator/` | Translator-specific tests | +| `tests/security/` | Security regressions | +| `tests/load/` | Load / stress tests | +| `tests/golden-set/` | Reference outputs for translator regressions | +| `tests/helpers/`, `tests/fixtures/`, `tests/manual/`, `tests/scratch_test.mjs` | Support | -#### Architecture +Common commands: -```mermaid -graph TD - subgraph "Request Translation" - A["Claude โ†’ OpenAI"] - B["Gemini โ†’ OpenAI"] - C["Antigravity โ†’ OpenAI"] - D["OpenAI Responses โ†’ OpenAI"] - E["OpenAI โ†’ Claude"] - F["OpenAI โ†’ Gemini"] - G["OpenAI โ†’ Kiro"] - H["OpenAI โ†’ Cursor"] - end - - subgraph "Response Translation" - I["Claude โ†’ OpenAI"] - J["Gemini โ†’ OpenAI"] - K["Kiro โ†’ OpenAI"] - L["Cursor โ†’ OpenAI"] - M["OpenAI โ†’ Claude"] - N["OpenAI โ†’ Antigravity"] - O["OpenAI โ†’ Responses"] - end -``` - -| Directory | Files | Description | -| ------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `request/` | 8 translators | Convert request bodies between formats. Each file self-registers via `register(from, to, fn)` on import. | -| `response/` | 7 translators | Convert streaming response chunks between formats. Handles SSE event types, thinking blocks, tool calls. | -| `helpers/` | 6 helpers | Shared utilities: `claudeHelper` (system prompt extraction, thinking config), `geminiHelper` (parts/contents mapping), `openaiHelper` (format filtering), `toolCallHelper` (ID generation, missing response injection), `maxTokensHelper`, `responsesApiHelper`. | -| `index.ts` | โ€” | Translation engine: `translateRequest()`, `translateResponse()`, state management, registry. | -| `formats.ts` | โ€” | Format constants: `OPENAI`, `CLAUDE`, `GEMINI`, `ANTIGRAVITY`, `KIRO`, `CURSOR`, `OPENAI_RESPONSES`. | - -#### Key Design: Self-Registering Plugins - -```javascript -// Each translator file calls register() on import: -import { register } from "../index.js"; -register("claude", "openai", translateClaudeToOpenAI); - -// The index.js imports all translator files, triggering registration: -import "./request/claude-to-openai.js"; // โ† self-registers -``` +| Command | What it runs | +| -------------------------------------------------------- | ---------------------------------------------------------------- | +| `npm run test:unit` | All `tests/unit/*.test.ts` via Node test runner (concurrency 10) | +| `npm run test:vitest` | Vitest suite (MCP, autoCombo, cache) | +| `npm run test:e2e` | Playwright UI suite | +| `npm run test:protocols:e2e` | MCP + A2A protocol e2e | +| `npm run test:coverage` | Coverage gate (โ‰ฅ60% lines/statements/functions/branches) | +| `node --import tsx/esm --test tests/unit/.test.ts` | Single file run | --- -### 4.6 Utils (`open-sse/utils/`) +## 8. `scripts/` -| File | Purpose | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `error.ts` | Error response building (OpenAI-compatible format), upstream error parsing, Antigravity retry-time extraction from error messages, SSE error streaming. | -| `stream.ts` | **SSE Transform Stream** โ€” the core streaming pipeline. Two modes: `TRANSLATE` (full format translation) and `PASSTHROUGH` (normalize + extract usage). Handles chunk buffering, usage estimation, content length tracking. Per-stream encoder/decoder instances avoid shared state. | -| `streamHelpers.ts` | Low-level SSE utilities: `parseSSELine` (whitespace-tolerant), `hasValuableContent` (filters empty chunks for OpenAI/Claude/Gemini), `fixInvalidId`, `formatSSE` (format-aware SSE serialization with `perf_metrics` cleanup). | -| `usageTracking.ts` | Token usage extraction from any format (Claude/OpenAI/Gemini/Responses), estimation with separate tool/message char-per-token ratios, buffer addition (2000 tokens safety margin), format-specific field filtering, console logging with ANSI colors. | -| `requestLogger.ts` | Legacy file-based request logging helper kept for compatibility. Current deployments should prefer `APP_LOG_TO_FILE` for application logs and the call log pipeline for persisted request artifacts. | -| `bypassHandler.ts` | Intercepts specific patterns from Claude CLI (title extraction, warmup, count) and returns fake responses without calling any provider. Supports both streaming and non-streaming. Intentionally limited to Claude CLI scope. | -| `networkProxy.ts` | Resolves outbound proxy URL for a given provider with precedence: provider-specific config โ†’ global config โ†’ environment variables (`HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY`). Supports `NO_PROXY` exclusions. Caches config for 30s. | +42 scripts. Highlights: -#### SSE Streaming Pipeline - -```mermaid -flowchart TD - A["Provider SSE stream"] --> B["TextDecoder\n(per-stream instance)"] - B --> C["Buffer lines\n(split on newline)"] - C --> D["parseSSELine()\n(trim whitespace, parse JSON)"] - D --> E{"Mode?"} - E -->|TRANSLATE| F["translateResponse()\ntarget โ†’ OpenAI โ†’ source"] - E -->|PASSTHROUGH| G["fixInvalidId()\nnormalize chunk"] - F --> H["hasValuableContent()\nfilter empty chunks"] - G --> H - H -->|"Has content"| I["extractUsage()\ntrack token counts"] - H -->|"Empty"| J["Skip chunk"] - I --> K["formatSSE()\nserialize + clean perf_metrics"] - K --> L["TextEncoder\n(per-stream instance)"] - L --> M["Enqueue to\nclient stream"] - - style A fill:#f9f,stroke:#333 - style M fill:#9f9,stroke:#333 -``` - -#### Request Logger Session Structure - -``` -logs/ -โ””โ”€โ”€ claude_gemini_claude-sonnet_20260208_143045/ - โ”œโ”€โ”€ 1_req_client.json โ† Raw client request - โ”œโ”€โ”€ 2_req_source.json โ† After initial conversion - โ”œโ”€โ”€ 3_req_openai.json โ† OpenAI intermediate format - โ”œโ”€โ”€ 4_req_target.json โ† Final target format - โ”œโ”€โ”€ 5_res_provider.txt โ† Provider SSE chunks (streaming) - โ”œโ”€โ”€ 5_res_provider.json โ† Provider response (non-streaming) - โ”œโ”€โ”€ 6_res_openai.txt โ† OpenAI intermediate chunks - โ”œโ”€โ”€ 7_res_client.txt โ† Client-facing SSE chunks - โ””โ”€โ”€ 6_error.json โ† Error details (if any) -``` +- **Build / runtime**: `build-next-isolated.mjs`, `prepare-electron-standalone.mjs`, + `run-next.mjs`, `run-next-playwright.mjs`, `run-standalone.mjs`, + `standalone-server-ws.mjs`, `responses-ws-proxy.mjs`, `v1-ws-bridge.mjs`, + `runtime-env.mjs`, `bootstrap-env.mjs`, `smoke-electron-packaged.mjs`. +- **Checks**: `check-cycles.mjs`, `check-docs-sync.mjs`, + `check-route-validation.mjs`, `check-t11-any-budget.mjs`, + `check-pr-test-policy.mjs`, `check-supported-node-runtime.ts`, + `native-binary-compat.mjs`. +- **Generators / sync**: `generate-docs-index.mjs`, `sync-env.mjs`, + `sync-cursor-models.mjs`, `migrate-env.mjs`. +- **Install / publish**: `postinstall.mjs`, `postinstallSupport.mjs`, + `prepublish.ts`, `pack-artifact-policy.ts`, `validate-pack-artifact.ts`, + `uninstall.mjs`. +- **Test runners**: `run-playwright-tests.mjs`, `run-ecosystem-tests.mjs`, + `run-protocol-clients-tests.mjs`, `test-report-summary.mjs`. +- **Misc**: `healthcheck.mjs`, `dbsetup.js`, `system-info.mjs`, + `cursor-tap.cjs`, `scratch.mjs`, `i18n_autotranslate.py`, + `check_translations.py`, `validate_translation.py`. --- -### 4.7 Application Layer (`src/`) +## 9. Request Pipeline (Summary) -| Directory | Purpose | -| ------------- | ---------------------------------------------------------------------- | -| `src/app/` | Web UI, API routes, Express middleware, OAuth callback handlers | -| `src/lib/` | Database access (`localDb.ts`, `usageDb.ts`), authentication, shared | -| `src/mitm/` | Man-in-the-middle proxy utilities for intercepting provider traffic | -| `src/models/` | Database model definitions | -| `src/shared/` | Wrappers around open-sse functions (provider, stream, error, etc.) | -| `src/sse/` | SSE endpoint handlers that wire the open-sse library to Express routes | -| `src/store/` | Application state management | - -#### Notable API Routes - -| Route | Methods | Purpose | -| --------------------------------------------- | --------------- | ------------------------------------------------------------------------------------- | -| `/api/provider-models` | GET/POST/DELETE | CRUD for custom models per provider | -| `/api/models/catalog` | GET | Aggregated catalog of all models (chat, embedding, image, custom) grouped by provider | -| `/api/settings/proxy` | GET/PUT/DELETE | Hierarchical outbound proxy configuration (`global/providers/combos/keys`) | -| `/api/settings/proxy/test` | POST | Validates proxy connectivity and returns public IP/latency | -| `/v1/providers/[provider]/chat/completions` | POST | Dedicated per-provider chat completions with model validation | -| `/v1/providers/[provider]/embeddings` | POST | Dedicated per-provider embeddings with model validation | -| `/v1/providers/[provider]/images/generations` | POST | Dedicated per-provider image generation with model validation | -| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist management | -| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget configuration (passthrough/auto/custom/adaptive) | -| `/api/settings/system-prompt` | GET/PUT | Global system prompt injection for all requests | -| `/api/sessions` | GET | Active session tracking and metrics | -| `/api/rate-limits` | GET | Per-account rate limit status | - ---- - -## 5. Key Design Patterns - -### 5.1 Hub-and-Spoke Translation - -All formats translate through **OpenAI format as the hub**. Adding a new provider only requires writing **one pair** of translators (to/from OpenAI), not N pairs. - -### 5.2 Executor Strategy Pattern - -Each provider has a dedicated executor class inheriting from `BaseExecutor`. The factory in `executors/index.ts` selects the right one at runtime. - -### 5.3 Self-Registering Plugin System - -Translator modules register themselves on import via `register()`. Adding a new translator is just creating a file and importing it. - -### 5.4 Account Fallback with Exponential Backoff - -When a provider returns 429/401/500, the system can switch to the next account, applying exponential cooldowns (1s โ†’ 2s โ†’ 4s โ†’ max 2min). - -### 5.5 Combo Model Chains - -A "combo" groups multiple `provider/model` strings. If the first fails, fallback to the next automatically. - -### 5.6 Stateful Streaming Translation - -Response translation maintains state across SSE chunks (thinking block tracking, tool call accumulation, content block indexing) via the `initState()` mechanism. - -### 5.7 Usage Safety Buffer - -A 2000-token buffer is added to reported usage to prevent clients from hitting context window limits due to overhead from system prompts and format translation. - ---- - -## 6. Supported Formats - -| Format | Direction | Identifier | -| ----------------------- | --------------- | ------------------ | -| OpenAI Chat Completions | source + target | `openai` | -| OpenAI Responses API | source + target | `openai-responses` | -| Anthropic Claude | source + target | `claude` | -| Google Gemini | source + target | `gemini` | -| Google Gemini CLI | target only | `gemini-cli` | -| Antigravity | source + target | `antigravity` | -| AWS Kiro | target only | `kiro` | -| Cursor | target only | `cursor` | - ---- - -## 7. Supported Providers - -| Provider | Auth Method | Executor | Key Notes | -| ------------------------ | ---------------------- | ----------- | --------------------------------------------- | -| Anthropic Claude | API key or OAuth | Default | Uses `x-api-key` header | -| Google Gemini | API key or OAuth | Default | Uses `x-goog-api-key` header | -| Google Gemini CLI | OAuth | GeminiCLI | Uses `streamGenerateContent` endpoint | -| Antigravity | OAuth | Antigravity | Multi-URL fallback, custom retry parsing | -| OpenAI | API key | Default | Standard Bearer auth | -| Codex | OAuth | Codex | Injects system instructions, manages thinking | -| GitHub Copilot | OAuth + Copilot token | Github | Dual token, VSCode header mimicking | -| Kiro (AWS) | AWS SSO OIDC or Social | Kiro | Binary EventStream parsing | -| Cursor IDE | Checksum auth | Cursor | Protobuf encoding, SHA-256 checksums | -| Qwen | OAuth | Default | Standard auth | -| Qoder | OAuth (Basic + Bearer) | Default | Dual auth header | -| OpenRouter | API key | Default | Standard Bearer auth | -| GLM, Kimi, MiniMax | API key | Default | Claude-compatible, use `x-api-key` | -| `openai-compatible-*` | API key | Default | Dynamic: any OpenAI-compatible endpoint | -| `anthropic-compatible-*` | API key | Default | Dynamic: any Claude-compatible endpoint | - ---- - -## 8. Data Flow Summary - -### Streaming Request - -```mermaid -flowchart LR - A["Client"] --> B["detectFormat()"] - B --> C["translateRequest()\nsource โ†’ OpenAI โ†’ target"] - C --> D["Executor\nbuildUrl + buildHeaders"] - D --> E["fetch(providerURL)"] - E --> F["createSSEStream()\nTRANSLATE mode"] - F --> G["parseSSELine()"] - G --> H["translateResponse()\ntarget โ†’ OpenAI โ†’ source"] - H --> I["extractUsage()\n+ addBuffer"] - I --> J["formatSSE()"] - J --> K["Client receives\ntranslated SSE"] - K --> L["logUsage()\nsaveRequestUsage()"] +``` +Client request + โ†’ /v1/chat/completions (route.ts) + CORS preflight check + Zod validation (chatCompletionsSchema in shared/validation/schemas.ts) + Auth (extractApiKey + isValidApiKey OR requireManagementAuth) + Policy engine (src/server/authz/pipeline.ts) + Guardrails (PII masker, prompt injection, vision bridge) + โ†’ handleChatCore() (open-sse/handlers/chatCore.ts) + Cache check (semantic + read cache) + Rate limit (rateLimitManager, accountSemaphore) + Combo routing (if model resolves to a combo) + comboResolver โ†’ loop per target โ†’ handleSingleModel() + translateRequest() (open-sse/translator/request/*) + getExecutor(providerId).execute() (open-sse/executors/*) + fetch upstream โ†’ retry/backoff via accountFallback + translateResponse() (open-sse/translator/response/*) + SSE stream OR JSON response + If Responses API: TransformStream via open-sse/transformer/responsesTransformer.ts + โ†’ Compliance audit (src/lib/compliance/) + โ†’ Response to client ``` -### Non-Streaming Request +### Resilience runtime state (three mechanisms) -```mermaid -flowchart LR - A["Client"] --> B["detectFormat()"] - B --> C["translateRequest()\nsource โ†’ OpenAI โ†’ target"] - C --> D["Executor.execute()"] - D --> E["translateResponse()\ntarget โ†’ OpenAI โ†’ source"] - E --> F["Return JSON\nresponse"] -``` +| Mechanism | Scope | Where | +| ------------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------ | +| Provider circuit breaker | Whole provider | `src/shared/utils/circuitBreaker.ts`, persisted in `domain_circuit_breakers` | +| Connection cooldown | One account/key | `markAccountUnavailable()` in `src/sse/services/auth.ts`; consumed by `accountFallback.checkFallbackError()` | +| Model lockout | Provider + connection + model | `open-sse/services/accountFallback.ts`, persisted in `domain_lockout_state` | -### Bypass Flow (Claude CLI) +See [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) and the dedicated section in +[CLAUDE.md](../CLAUDE.md). -```mermaid -flowchart LR - A["Claude CLI request"] --> B{"Match bypass\npattern?"} - B -->|"Title/Warmup/Count"| C["Generate fake\nOpenAI response"] - B -->|"No match"| D["Normal flow"] - C --> E["Translate to\nsource format"] - E --> F["Return without\ncalling provider"] -``` +--- + +## 10. How to Contribute + +### Add a new provider + +1. Register in `src/shared/constants/providers.ts` (Zod-validated at load). +2. Add an executor in `open-sse/executors/` if custom logic is required + (extend `BaseExecutor`). +3. Add a translator in `open-sse/translator/` if it does not speak OpenAI format. +4. If OAuth-based, add config under `src/lib/oauth/providers/` and + `src/lib/oauth/services/`. +5. Register models in `open-sse/config/providerRegistry.ts` (or the format-specific + registry under `open-sse/config/`). +6. Write tests under `tests/unit/`. + +### Add a new API route + +1. Create `src/app/api/your-route/route.ts`. +2. Follow the pattern: CORS โ†’ Zod body validation โ†’ auth โ†’ handler delegation. +3. If new request shape: add the Zod schema in `src/shared/validation/schemas.ts`. +4. If management-only: add the path to `src/shared/constants/publicApiRoutes.ts` + (denylist for the public API surface). +5. Add tests under `tests/unit/`. +6. Update `docs/API_REFERENCE.md` and `docs/openapi.yaml`. + +### Add a new DB module + +1. Create `src/lib/db/yourModule.ts` and import `getDbInstance()` from `./core.ts`. +2. Export CRUD functions for your domain. +3. If new tables: add a migration under `src/lib/db/migrations/`, numbered + sequentially, idempotent, transactional. +4. Re-export from `src/lib/localDb.ts` (re-export only โ€” **no logic**). +5. Add tests under `tests/unit/`. + +### Add a new MCP tool + +1. Add the tool definition under `open-sse/mcp-server/tools/` (or extend + `open-sse/mcp-server/schemas/tools.ts`). +2. Assign the appropriate scope(s) in `src/shared/constants/mcpScopes.ts`. +3. Register the tool in `open-sse/mcp-server/server.ts`. +4. Add tests under `open-sse/mcp-server/__tests__/`. +5. Update [MCP-SERVER.md](./MCP-SERVER.md). + +### Add a new A2A skill + +See [A2A-SERVER.md ยง Adding a New Skill](./A2A-SERVER.md). Skills live in +`src/lib/a2a/skills/` and are registered through the A2A task manager. + +--- + +## 11. Conventions + +- **Code style**: 2-space indent, double quotes, 100 char width, semicolons, + `es5` trailing commas โ€” enforced by Prettier via `lint-staged`. +- **Imports**: external โ†’ internal (`@/`, `@omniroute/open-sse`) โ†’ relative. +- **Naming**: files `camelCase` or `kebab-case`, components `PascalCase`, + constants `UPPER_SNAKE`. +- **ESLint**: `no-eval`, `no-implied-eval`, `no-new-func` = `error` everywhere; + `no-explicit-any` = `warn` in `open-sse/` and `tests/`, error elsewhere. +- **TypeScript**: `strict: false` (legacy posture). Prefer explicit types over + inference for cross-module boundaries. +- **Database**: never write raw SQL in routes or handlers โ€” always go through + `src/lib/db/` modules. Never add logic to `src/lib/localDb.ts`. +- **Errors**: try/catch with specific error types, log with pino context. Never + silently swallow errors in SSE streams; use abort signals for cleanup. +- **Security**: never use `eval()` / `new Function()` / implied eval. Validate + all inputs with Zod. Encrypt credentials at rest (AES-256-GCM). Keep + `src/shared/constants/upstreamHeaders.ts` denylist aligned with the + sanitize/validation layer. +- **Commits**: Conventional Commits โ€” `feat(scope): subject`. Allowed scopes: + `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, + `a2a`, `memory`, `skills`. +- **Branches**: prefixes `feat/`, `fix/`, `refactor/`, `docs/`, `test/`, + `chore/`. Never commit directly to `main`. +- **Husky**: pre-commit runs `lint-staged` + `check:docs-sync` + + `check:any-budget:t11`; pre-push runs `npm run test:unit`. + +--- + +## 12. Hard Rules (from CLAUDE.md) + +1. Never commit secrets or credentials. +2. Never add logic to `src/lib/localDb.ts`. +3. Never use `eval()` / `new Function()` / implied eval. +4. Never commit directly to `main`. +5. Never write raw SQL in routes โ€” always go through `src/lib/db/` modules. +6. Never silently swallow errors in SSE streams. +7. Always validate inputs with Zod schemas. +8. Always include tests when changing production code. +9. Coverage must stay โ‰ฅ 60% (statements, lines, functions, branches). + +--- + +## 13. See Also + +- [ARCHITECTURE.md](./ARCHITECTURE.md) โ€” high-level architecture and module + responsibilities. +- [API_REFERENCE.md](./API_REFERENCE.md) โ€” public + management API reference. +- [FEATURES.md](./FEATURES.md) โ€” feature matrix and version highlights. +- [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) โ€” circuit breaker, cooldown, + lockout deep dive. +- [AUTO-COMBO.md](./AUTO-COMBO.md) โ€” Auto Combo scoring and strategies. +- [MCP-SERVER.md](./MCP-SERVER.md) โ€” full MCP tool catalog + transports. +- [A2A-SERVER.md](./A2A-SERVER.md) โ€” A2A protocol skills and discovery. +- [COMPRESSION_GUIDE.md](./COMPRESSION_GUIDE.md) โ€” RTK + Caveman compression. +- [CLI-TOOLS.md](./CLI-TOOLS.md) โ€” CLI integrations. +- [ELECTRON_GUIDE.md](./ELECTRON_GUIDE.md) (if present), [DOCKER_GUIDE.md](./DOCKER_GUIDE.md), [FLY_IO_DEPLOYMENT_GUIDE.md](./FLY_IO_DEPLOYMENT_GUIDE.md), [VM_DEPLOYMENT_GUIDE.md](./VM_DEPLOYMENT_GUIDE.md), [TERMUX_GUIDE.md](./TERMUX_GUIDE.md), [PWA_GUIDE.md](./PWA_GUIDE.md) โ€” deployment targets. +- [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) โ€” common operational issues. +- [CONTRIBUTING.md](../CONTRIBUTING.md) โ€” contributor workflow. +- [CLAUDE.md](../CLAUDE.md) โ€” repo rules for Claude Code (the source of truth + for many of the conventions above). +- [AGENTS.md](../AGENTS.md) โ€” deeper architecture reference used by agents. diff --git a/docs/I18N.md b/docs/I18N.md index 50525515fc..458e5df371 100644 --- a/docs/I18N.md +++ b/docs/I18N.md @@ -6,24 +6,26 @@ OmniRoute supports **30 languages** with full dashboard UI translation, translat ## Quick Reference -| Task | Command | -|------|---------| -| Generate translations | `node scripts/i18n/generate-multilang.mjs messages` | -| Translate docs (LLM) | `python3 scripts/i18n_autotranslate.py --api-url --api-key --model ` | -| Validate a locale | `python3 scripts/validate_translation.py quick -l cs` | -| Check code keys | `python3 scripts/check_translations.py` | -| Generate QA report | `node scripts/i18n/generate-qa-checklist.mjs` | -| Visual QA (Playwright) | `node scripts/i18n/run-visual-qa.mjs` | +| Task | Command | +| ---------------------- | --------------------------------------------------------------------------------------- | +| Generate translations | `node scripts/i18n/generate-multilang.mjs messages` | +| Translate docs (LLM) | `python3 scripts/i18n_autotranslate.py --api-url --api-key --model ` | +| Validate a locale | `python3 scripts/validate_translation.py quick -l cs` | +| Check code keys | `python3 scripts/check_translations.py` | +| Generate QA report | `node scripts/i18n/generate-qa-checklist.mjs` | +| Visual QA (Playwright) | `node scripts/i18n/run-visual-qa.mjs` | ## Architecture ### Source of Truth + - **UI strings**: `src/i18n/messages/en.json` (English source, ~2800 keys) - **Locale files**: `src/i18n/messages/{locale}.json` (30 translations) - **Framework**: `next-intl` with cookie-based locale resolution - **Config**: `src/i18n/config.ts` โ€” defines all 30 locales, language names, flags ### Runtime Flow + 1. User selects language โ†’ `NEXT_LOCALE` cookie set 2. `src/i18n/request.ts` resolves locale: cookie โ†’ `Accept-Language` header โ†’ fallback `en` 3. Dynamic import loads `messages/{locale}.json` @@ -31,44 +33,46 @@ OmniRoute supports **30 languages** with full dashboard UI translation, translat ### Supported Locales -| Code | Language | RTL | Google Translate Code | -|------|----------|-----|----------------------| -| `ar` | ุงู„ุนุฑุจูŠุฉ | Yes | `ar` | -| `bg` | ะ‘ัŠะปะณะฐั€ัะบะธ | No | `bg` | -| `cs` | ฤŒeลกtina | No | `cs` | -| `da` | Dansk | No | `da` | -| `de` | Deutsch | No | `de` | -| `es` | Espaรฑol | No | `es` | -| `fi` | Suomi | No | `fi` | -| `fr` | Franรงais | No | `fr` | -| `he` | ืขื‘ืจื™ืช | Yes | `iw` | -| `hi` | เคนเคฟเคจเฅเคฆเฅ€ | No | `hi` | -| `hu` | Magyar | No | `hu` | -| `id` | Bahasa Indonesia | No | `id` | -| `it` | Italiano | No | `it` | -| `ja` | ๆ—ฅๆœฌ่ชž | No | `ja` | -| `ko` | ํ•œ๊ตญ์–ด | No | `ko` | -| `ms` | Bahasa Melayu | No | `ms` | -| `nl` | Nederlands | No | `nl` | -| `no` | Norsk | No | `no` | -| `phi` | Filipino | No | `tl` | -| `pl` | Polski | No | `pl` | -| `pt` | Portuguรชs (Portugal) | No | `pt` | -| `pt-BR` | Portuguรชs (Brasil) | No | `pt` | -| `ro` | Romรขnฤƒ | No | `ro` | -| `ru` | ะ ัƒััะบะธะน | No | `ru` | -| `sk` | Slovenฤina | No | `sk` | -| `sv` | Svenska | No | `sv` | -| `th` | เน„เธ—เธข | No | `th` | -| `tr` | Tรผrkรงe | No | `tr` | -| `uk-UA` | ะฃะบั€ะฐั—ะฝััŒะบะฐ | No | `uk` | -| `vi` | Tiแบฟng Viแป‡t | No | `vi` | -| `zh-CN` | ไธญๆ–‡ (็ฎ€ไฝ“) | No | `zh-CN` | +| Code | Language | RTL | Google Translate Code | +| ------- | -------------------- | --- | --------------------- | +| `ar` | ุงู„ุนุฑุจูŠุฉ | Yes | `ar` | +| `bg` | ะ‘ัŠะปะณะฐั€ัะบะธ | No | `bg` | +| `cs` | ฤŒeลกtina | No | `cs` | +| `da` | Dansk | No | `da` | +| `de` | Deutsch | No | `de` | +| `es` | Espaรฑol | No | `es` | +| `fi` | Suomi | No | `fi` | +| `fr` | Franรงais | No | `fr` | +| `he` | ืขื‘ืจื™ืช | Yes | `iw` | +| `hi` | เคนเคฟเคจเฅเคฆเฅ€ | No | `hi` | +| `hu` | Magyar | No | `hu` | +| `id` | Bahasa Indonesia | No | `id` | +| `it` | Italiano | No | `it` | +| `ja` | ๆ—ฅๆœฌ่ชž | No | `ja` | +| `ko` | ํ•œ๊ตญ์–ด | No | `ko` | +| `ms` | Bahasa Melayu | No | `ms` | +| `nl` | Nederlands | No | `nl` | +| `no` | Norsk | No | `no` | +| `phi` | Filipino | No | `tl` | +| `pl` | Polski | No | `pl` | +| `pt` | Portuguรชs (Portugal) | No | `pt` | +| `pt-BR` | Portuguรชs (Brasil) | No | `pt` | +| `ro` | Romรขnฤƒ | No | `ro` | +| `ru` | ะ ัƒััะบะธะน | No | `ru` | +| `sk` | Slovenฤina | No | `sk` | +| `sv` | Svenska | No | `sv` | +| `th` | เน„เธ—เธข | No | `th` | +| `tr` | Tรผrkรงe | No | `tr` | +| `uk-UA` | ะฃะบั€ะฐั—ะฝััŒะบะฐ | No | `uk` | +| `vi` | Tiแบฟng Viแป‡t | No | `vi` | +| `zh-CN` | ไธญๆ–‡ (็ฎ€ไฝ“) | No | `zh-CN` | ## Adding a New Language ### 1. Register the Locale + Edit `src/i18n/config.ts`: + ```ts // Add to LOCALES array "xx", @@ -77,7 +81,9 @@ Edit `src/i18n/config.ts`: ``` ### 2. Add to Generator + Edit `scripts/i18n/generate-multilang.mjs` โ€” add entry to `LOCALE_SPECS`: + ```js { code: "xx", @@ -91,24 +97,30 @@ Edit `scripts/i18n/generate-multilang.mjs` โ€” add entry to `LOCALE_SPECS`: ``` ### 3. Generate Initial Translation + ```bash node scripts/i18n/generate-multilang.mjs messages ``` + This creates `src/i18n/messages/xx.json` auto-translated from `en.json` via Google Translate. ### 4. Review & Fix Auto-Translations + Auto-translations are a starting point. Review manually for: + - Technical accuracy - Context-appropriate terminology - Proper handling of placeholders (`{count}`, `{value}`, etc.) ### 5. Validate + ```bash python3 scripts/validate_translation.py quick -l xx python3 scripts/validate_translation.py diff common -l xx ``` ### 6. Generate Translated Documentation + ```bash node scripts/i18n/generate-multilang.mjs docs ``` @@ -123,15 +135,16 @@ node scripts/i18n/generate-multilang.mjs docs node scripts/i18n/generate-multilang.mjs [messages|readme|docs|all] ``` -| Mode | What it does | -|------|-------------| -| `messages` | Translates missing keys in `src/i18n/messages/{locale}.json` from `en.json` | -| `readme` | Translates `README.md` into all locales as `README.{code}.md` in project root | -| `docs` | Translates `DOC_SOURCE_FILES` into `docs/i18n/{locale}/{docName}` | -| `all` | Runs all three modes | +| Mode | What it does | +| ---------- | ----------------------------------------------------------------------------- | +| `messages` | Translates missing keys in `src/i18n/messages/{locale}.json` from `en.json` | +| `readme` | Translates `README.md` into all locales as `README.{code}.md` in project root | +| `docs` | Translates `DOC_SOURCE_FILES` into `docs/i18n/{locale}/{docName}` | +| `all` | Runs all three modes | **Features:** -- **Text protection**: Masks code blocks (```` ``` ````), inline code (`` ` ``), markdown links/images (`[text](url)`), HTML tags, tables, and ICU placeholders (`{count}`, `{value}`, `{total}`, etc.) before translation, then restores them + +- **Text protection**: Masks code blocks (` ``` `), inline code (`` ` ``), markdown links/images (`[text](url)`), HTML tags, tables, and ICU placeholders (`{count}`, `{value}`, `{total}`, etc.) before translation, then restores them - **Chunked batching**: Joins multiple strings with `__OMNIROUTE_I18N_SEPARATOR__` delimiters to minimize API calls (max 1800 chars per request) - **In-memory cache**: Avoids redundant API calls for repeated strings within a session - **Retry logic**: Exponential backoff (up to 5 attempts with 300ms ร— attempt delay) for 429/5xx errors @@ -139,6 +152,7 @@ node scripts/i18n/generate-multilang.mjs [messages|readme|docs|all] - **Skip existing**: If target file already exists, it is NOT overwritten **Important behaviors:** + - `docs/i18n/README.md` is **regenerated** each run โ€” it's an auto-generated index of all docs - Root `README.{code}.md` files are only created if they don't exist (skips locales in `EXISTING_README_CODES`) - Language bars (`๐ŸŒ **Languages:** ...`) are automatically inserted/updated in all translated docs @@ -155,6 +169,7 @@ python3 scripts/i18n_autotranslate.py \ ``` **Features:** + - Scans `docs/i18n/` markdown files for English paragraphs - Skips code blocks, tables, and already-translated content - Sends paragraphs to LLM with technical translation system prompt @@ -189,6 +204,7 @@ python3 scripts/validate_translation.py -l cs ``` **Detects:** + - **Missing keys** โ€” keys in `en.json` but not in locale file - **Extra keys** โ€” keys in locale file but not in `en.json` - **Untranslated keys** โ€” keys where locale value equals English source (excluding allowlist) @@ -228,6 +244,7 @@ node scripts/i18n/generate-qa-checklist.mjs ``` **Checks:** + - Fixed-width class usage (overflow risk) - Directional left/right classes (RTL risk) - Clipping-prone patterns @@ -252,6 +269,7 @@ QA_ROUTES=/dashboard/settings,/dashboard/providers node scripts/i18n/run-visual- ``` **Detects:** + - Text overflow - Element clipping - RTL layout mismatches @@ -279,6 +297,7 @@ Allowlist of keys that should remain identical to English source. Used by `valid ``` **What belongs here:** + - Brand/product names: `landing.brandName`, `common.social-github` - Technical terms/acronyms: `health.cpu`, `mcpDashboard.pid`, `settings.ai` - ICU/format strings: `apiManager.modelsCount`, `health.millisecondsShort` @@ -307,6 +326,7 @@ python3 scripts/validate_translation.py quick -l '${{ matrix.lang }}' ``` **Dashboard output:** + ``` ## ๐ŸŒ Translations | Metric | Value | @@ -360,6 +380,7 @@ docs/ ## Best Practices ### When Editing Translations + 1. **Always edit `en.json` first** โ€” it's the source of truth 2. **Run `generate-multilang.mjs messages`** to propagate new keys to all locales 3. **Review auto-translations** โ€” Google Translate is a starting point, not final @@ -367,11 +388,13 @@ docs/ 5. **Update `untranslatable-keys.json`** if a key should remain in English ### Placeholder Safety + - ICU placeholders (`{count}`, `{value}`, `{total}`, `{seconds}`) must be preserved exactly - Plural formats (`{count, plural, one {# model} other {# models}}`) must maintain structure - The validator detects placeholder mismatches automatically ### Adding New Translation Keys in Code + ```tsx // Use namespaced keys const t = useTranslations("settings"); @@ -382,6 +405,7 @@ python3 scripts/check_translations.py --verbose ``` ### RTL Considerations + - Arabic (`ar`) and Hebrew (`he`) are RTL locales - Avoid hardcoded `left`/`right` CSS โ€” use `start`/`end` logical properties - Visual QA catches RTL layout mismatches via `run-visual-qa.mjs` @@ -389,21 +413,29 @@ python3 scripts/check_translations.py --verbose ## Known Issues & History ### `in.json` โ†’ `hi.json` Fix + The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This created an orphaned `in.json` duplicate of `hi.json`. Fixed by changing `code: "in"` to `code: "hi"` in `generate-multilang.mjs` and removing the orphaned file. +> โš ๏ธ **Audit (2026-05-13):** The `docs/i18n/in/` directory still exists on disk (full duplicate of `hi/`). Translation generator no longer writes to it, but the historical tree was not pruned. Safe to delete with `rm -rf docs/i18n/in/` after confirming no external links reference the old path. + ### `docs/i18n/README.md` Is Auto-Generated + The `docs/i18n/README.md` file is completely regenerated by `generate-multilang.mjs docs`. Any manual edits will be lost. Use `docs/I18N.md` (this file) for hand-written documentation that should persist. ### External Untranslatable Keys List + The `untranslatable-keys.json` allowlist was moved from an inline Python set in `validate_translation.py` to an external JSON file for easier maintenance. The validator loads it at runtime. ### `generate-multilang.mjs` Hindi Code Fix + The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This was introduced in upstream commit `952b0b22c` by `diegosouzapw`. Fixed by changing `code: "in"` to `code: "hi"` in the `LOCALE_SPECS` array and removing the orphaned `in.json` file. ### `validate_translation.py` Ignored Count Output + The `quick` check now displays the count of ignored keys from `untranslatable-keys.json`: + ``` Missing: 0 Untranslated: 0 -Ignored (UNTRANSLATABLE_KEYS): 236 +Ignored (UNTRANSLATABLE_KEYS): ``` diff --git a/docs/TERMUX_GUIDE.md b/docs/TERMUX_GUIDE.md index 28012906e9..bdc13f15d3 100644 --- a/docs/TERMUX_GUIDE.md +++ b/docs/TERMUX_GUIDE.md @@ -12,6 +12,8 @@ pkg upgrade pkg install nodejs-lts python build-essential git ``` +> **Node.js version:** OmniRoute requires Node `>=20.20.2 <21 || >=22.22.2 <23 || >=24.0.0 <27` (per `engines` in `package.json`). Termux's `nodejs-lts` typically ships Node 20 LTS, which is compatible. If `node --version` reports an older line, install `pkg install nodejs` (current) and verify the major matches a supported range. + If native package compilation fails, rerun the `pkg install` command above and then retry the OmniRoute install. ## Install diff --git a/package.json b/package.json index 5ceed4c903..03cee3da5f 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,10 @@ "check:route-validation:t06": "node scripts/check-route-validation.mjs", "check:any-budget:t11": "node scripts/check-t11-any-budget.mjs", "check:docs-sync": "node scripts/check-docs-sync.mjs", + "check:env-doc-sync": "node scripts/check-env-doc-sync.mjs", + "check:docs-counts": "node scripts/check-docs-counts-sync.mjs", + "check:deprecated-versions": "node scripts/check-deprecated-versions.mjs", + "check:docs-all": "npm run check:docs-sync && npm run check:docs-counts && npm run check:env-doc-sync && npm run check:deprecated-versions", "check:node-runtime": "node --import tsx/esm scripts/check-supported-node-runtime.ts", "check:pack-artifact": "node --import tsx/esm scripts/validate-pack-artifact.ts", "audit:deps": "npm audit --audit-level=moderate && npm run audit:electron", diff --git a/scripts/check-deprecated-versions.mjs b/scripts/check-deprecated-versions.mjs new file mode 100644 index 0000000000..990db76fe0 --- /dev/null +++ b/scripts/check-deprecated-versions.mjs @@ -0,0 +1,130 @@ +#!/usr/bin/env node +// Detects hardcoded old versions / stale dates in docs that should follow the current release. +// Uses hardcoded regexes to avoid dynamic RegExp() (ReDoS concern flagged by semgrep). +// Exits 0 if clean, 1 (in --strict mode) if drift detected. +// +// Run: node scripts/check-deprecated-versions.mjs +// Strict: node scripts/check-deprecated-versions.mjs --strict + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, ".."); +const DOCS_DIR = path.join(ROOT, "docs"); +const PKG_JSON = path.join(ROOT, "package.json"); +const STRICT = process.argv.includes("--strict"); + +function currentVersion() { + try { + return JSON.parse(fs.readFileSync(PKG_JSON, "utf8")).version || "0.0.0"; + } catch { + return "0.0.0"; + } +} + +// Hardcoded set of obviously-stale version patterns. Update when bumping major/minor. +// These match any version <= v3.6.x or any pre-3 major. +const STALE_VERSION_PATTERNS = [ + /\bv?[12]\.\d+\.\d+\b/, // 1.x.x / 2.x.x + /\bv?3\.[0-6]\.\d+\b/, // 3.0.x..3.6.x +]; + +const SAFE_CONTEXTS = + /(historical|archive|legacy|previously|deprecated|since|introduced|was|originally|fix|fixed)/i; + +// Dates older than 60 days are considered stale for "Last updated" / "Last consolidated". +const STALE_DATE_DAYS = 60; +const today = new Date(); + +const LAST_UPDATED_RE = /Last (?:updated|consolidated|generated)[^\d]{0,30}(\d{4}-\d{2}-\d{2})/i; + +function isStaleDate(yyyy_mm_dd) { + const d = new Date(yyyy_mm_dd); + if (Number.isNaN(d.getTime())) return false; + const ageDays = (today - d) / (1000 * 60 * 60 * 24); + return ageDays > STALE_DATE_DAYS; +} + +const IGNORED_DIRS = new Set(["archive", "i18n", "superpowers"]); +const IGNORED_BASENAMES = new Set(["CHANGELOG.md", "RFC-AUTO-ASSESSMENT-DRAFT.md"]); + +function walkDocs(dir) { + const out = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (IGNORED_DIRS.has(entry.name)) continue; + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + out.push(...walkDocs(full)); + } else if (entry.isFile() && entry.name.endsWith(".md")) { + if (IGNORED_BASENAMES.has(entry.name)) continue; + out.push(full); + } + } + return out; +} + +function main() { + const cur = currentVersion(); + console.log(`Version drift report (current: v${cur})`); + console.log("=".repeat(40)); + + const files = walkDocs(DOCS_DIR); + let drift = 0; + + for (const file of files) { + const rel = path.relative(ROOT, file); + const txt = fs.readFileSync(file, "utf8"); + const lines = txt.split("\n"); + const issues = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.length > 500) continue; // skip very long lines (likely code blocks / data) + + // 1. Stale version refs + for (const re of STALE_VERSION_PATTERNS) { + const m = re.exec(line); + if (m && !SAFE_CONTEXTS.test(line)) { + issues.push({ + line: i + 1, + type: "stale-version", + match: m[0], + text: line.trim().slice(0, 100), + }); + break; + } + } + // 2. Stale "Last updated/consolidated/generated" dates + const dateMatch = LAST_UPDATED_RE.exec(line); + if (dateMatch && isStaleDate(dateMatch[1])) { + issues.push({ + line: i + 1, + type: "stale-date", + match: dateMatch[1], + text: line.trim().slice(0, 100), + }); + } + } + + if (issues.length > 0) { + console.log(`\n ${rel}`); + for (const iss of issues.slice(0, 5)) { + console.log(` L${iss.line} [${iss.type}] ${iss.match}: ${iss.text}`); + } + if (issues.length > 5) console.log(` ... and ${issues.length - 5} more`); + drift += issues.length; + } + } + + console.log(); + if (drift > 0) { + console.warn(`โš  ${drift} potential drift(s) detected across ${files.length} doc files.`); + if (STRICT) process.exit(1); + } else { + console.log(`โœ“ No drift detected across ${files.length} doc files.`); + } +} + +main(); diff --git a/scripts/check-docs-counts-sync.mjs b/scripts/check-docs-counts-sync.mjs new file mode 100644 index 0000000000..833d52c5a5 --- /dev/null +++ b/scripts/check-docs-counts-sync.mjs @@ -0,0 +1,115 @@ +#!/usr/bin/env node +// Validates that count-based assertions in docs match the actual code state. +// Examples checked: +// - executors count in open-sse/executors/ +// - routing strategies in src/shared/constants/routingStrategies.ts +// - OAuth providers in src/lib/oauth/providers/ +// - A2A skills in src/lib/a2a/skills/ +// - Cloud agents in src/lib/cloudAgent/agents/ +// +// Exits 0 on success, 1 on detected drift. +// Run: node scripts/check-docs-counts-sync.mjs + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, ".."); + +const COMMON_NON_IMPL_BASENAMES = new Set([ + "index.ts", + "index.mts", + "types.ts", + "base.ts", + "constants.ts", +]); + +function countFiles(dir, suffix = ".ts") { + const abs = path.join(ROOT, dir); + if (!fs.existsSync(abs)) return 0; + return fs + .readdirSync(abs) + .filter( + (f) => + f.endsWith(suffix) && + !f.endsWith(".test.ts") && + !f.startsWith("__") && + !COMMON_NON_IMPL_BASENAMES.has(f) + ).length; +} + +function countRoutingStrategies() { + const file = path.join(ROOT, "src", "shared", "constants", "routingStrategies.ts"); + if (!fs.existsSync(file)) return 0; + const txt = fs.readFileSync(file, "utf8"); + const m = txt.match(/ROUTING_STRATEGY_VALUES\s*=\s*\[([^\]]*)\]/); + if (!m) return 0; + return (m[1].match(/"[^"]+"/g) || []).length; +} + +function docContains(docPath, needle) { + const abs = path.join(ROOT, "docs", docPath); + if (!fs.existsSync(abs)) return false; + return fs.readFileSync(abs, "utf8").includes(needle); +} + +const checks = [ + { + label: "Executors count", + actual: countFiles("open-sse/executors"), + docKey: "executors", + docs: ["ARCHITECTURE.md", "CODEBASE_DOCUMENTATION.md"], + }, + { + label: "Routing strategies count", + actual: countRoutingStrategies(), + docKey: "strategies", + docs: ["AUTO-COMBO.md", "RESILIENCE_GUIDE.md"], + }, + { + label: "OAuth providers count", + actual: countFiles("src/lib/oauth/providers"), + docKey: "OAuth providers", + docs: ["ARCHITECTURE.md"], + }, + { + label: "A2A skills count", + actual: countFiles("src/lib/a2a/skills"), + docKey: "A2A skills", + docs: ["A2A-SERVER.md"], + }, + { + label: "Cloud agents count", + actual: countFiles("src/lib/cloudAgent/agents"), + docKey: "cloud agents", + docs: ["CLOUD_AGENT.md", "AGENT_PROTOCOLS_GUIDE.md"], + }, +]; + +let drift = 0; +console.log("Docs counts sync report"); +console.log("======================="); + +for (const c of checks) { + console.log(`\nโ€ข ${c.label}: ${c.actual} (real)`); + for (const doc of c.docs) { + const found = docContains(doc, String(c.actual)); + if (found) { + console.log(` โœ“ docs/${doc} mentions "${c.actual}"`); + } else { + console.log(` โš  docs/${doc} does NOT mention "${c.actual}" for ${c.docKey}`); + drift++; + } + } +} + +console.log(); +if (drift > 0) { + console.warn(`โš  ${drift} potential drift(s) detected. Review the docs above.`); + // Soft-fail by default (count-based heuristic can false-positive). + // To enforce, pass --strict. + if (process.argv.includes("--strict")) process.exit(1); +} else { + console.log("โœ“ All checks pass."); +} diff --git a/scripts/check-env-doc-sync.mjs b/scripts/check-env-doc-sync.mjs new file mode 100644 index 0000000000..8f03665604 --- /dev/null +++ b/scripts/check-env-doc-sync.mjs @@ -0,0 +1,131 @@ +#!/usr/bin/env node +// Validates that env vars referenced in code appear in .env.example AND in docs/ENVIRONMENT.md. +// Exits 0 on success, 1 on missing entries. Designed for use in pre-commit / CI. +// +// Run: node scripts/check-env-doc-sync.mjs +// Strict mode: node scripts/check-env-doc-sync.mjs --strict +// In strict mode, missing entries cause failure. In default mode, only summary is printed. + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { execSync } from "node:child_process"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, ".."); +const ENV_EXAMPLE = path.join(ROOT, ".env.example"); +const ENV_DOC = path.join(ROOT, "docs", "ENVIRONMENT.md"); + +const STRICT = process.argv.includes("--strict"); + +// Vars that are intentionally not documented or detected in code via dynamic patterns. +const IGNORE = new Set([ + "NODE_ENV", + "PATH", + "HOME", + "USER", + "PWD", + "SHELL", + "TERM", + "TZ", + "LANG", + "LC_ALL", + "CI", + "GITHUB_ACTIONS", + "RUNNER_OS", + // Add false positives here as discovered. +]); + +function readEnvExampleVars() { + if (!fs.existsSync(ENV_EXAMPLE)) { + console.error(`โœ— ${ENV_EXAMPLE} not found`); + process.exit(2); + } + const txt = fs.readFileSync(ENV_EXAMPLE, "utf8"); + const vars = new Set(); + for (const line of txt.split("\n")) { + // Match both "VAR=value" and "# VAR=value" (commented-out examples are still documented) + const m = line.match(/^#?\s*([A-Z][A-Z0-9_]+)\s*=/); + if (m) vars.add(m[1]); + } + return vars; +} + +function readEnvDocVars() { + if (!fs.existsSync(ENV_DOC)) { + console.error(`โœ— ${ENV_DOC} not found`); + process.exit(2); + } + const txt = fs.readFileSync(ENV_DOC, "utf8"); + const vars = new Set(); + // Match `VAR_NAME` in inline code or table cells. + for (const m of txt.matchAll(/`([A-Z][A-Z0-9_]{2,})`/g)) { + vars.add(m[1]); + } + return vars; +} + +function readCodeVars() { + const vars = new Set(); + let stdout; + try { + stdout = execSync( + "grep -rhoE 'process\\.env\\.[A-Z][A-Z0-9_]+' src/ open-sse/ bin/ scripts/ 2>/dev/null || true", + { cwd: ROOT, encoding: "utf8", maxBuffer: 20 * 1024 * 1024 } + ); + } catch (e) { + console.error(`โœ— grep failed: ${e.message}`); + process.exit(2); + } + for (const line of stdout.split("\n")) { + const m = line.match(/^process\.env\.([A-Z][A-Z0-9_]+)$/); + if (m && !IGNORE.has(m[1])) vars.add(m[1]); + } + return vars; +} + +function diff(set, against) { + return [...set].filter((v) => !against.has(v)).sort(); +} + +function main() { + const codeVars = readCodeVars(); + const exampleVars = readEnvExampleVars(); + const docVars = readEnvDocVars(); + + const inCodeMissingExample = diff(codeVars, exampleVars); + const inCodeMissingDoc = diff(codeVars, docVars); + const inExampleMissingDoc = diff(exampleVars, docVars); + const inExampleMissingCode = diff(exampleVars, codeVars); + + console.log("Env var sync report"); + console.log("==================="); + console.log(`Code references: ${codeVars.size} unique vars`); + console.log(`In .env.example: ${exampleVars.size} unique vars`); + console.log(`In docs/ENVIRONMENT.md: ${docVars.size} unique vars (heuristic)`); + console.log(); + + function printList(label, list) { + if (list.length === 0) { + console.log(` โœ“ ${label}: none`); + } else { + console.log(` โš  ${label}: ${list.length}`); + for (const v of list.slice(0, 30)) console.log(` - ${v}`); + if (list.length > 30) console.log(` ... and ${list.length - 30} more`); + } + } + + printList("In code but missing from .env.example", inCodeMissingExample); + printList("In code but missing from ENVIRONMENT.md", inCodeMissingDoc); + printList("In .env.example but missing from ENVIRONMENT.md", inExampleMissingDoc); + printList("In .env.example but not referenced in code (dead?)", inExampleMissingCode); + + const errors = inCodeMissingExample.length + inExampleMissingDoc.length; + if (STRICT && errors > 0) { + console.error(`\nโœ— ${errors} drift(s) detected (strict mode)`); + process.exit(1); + } + console.log(`\n${errors === 0 ? "โœ“" : "โš "} Done.`); +} + +main();