From 16fda78ee148ee828fdb16337d44378f842ae5a1 Mon Sep 17 00:00:00 2001 From: Felipe Almeman Date: Sat, 6 Jun 2026 15:43:49 -0300 Subject: [PATCH] docs: VS Code/Ollama endpoints + env & i18n tooling Splits the upstream-worthy docs and tooling out of #3073: - docs/reference/{API_REFERENCE,CLI-TOOLS}.md, docs/guides/SETUP_GUIDE.md, README.md: document the /api/v1/vscode/{token} endpoints and the tokenized compatibility base (example server IP shown as a placeholder) - scripts/build/bootstrap-env.mjs: env/secret bootstrap hardening - scripts/i18n/check-ui-keys-coverage.mjs: i18n key coverage tooling - .dockerignore: keep the English docs tree for the in-product Docs viewer (#2348) Deliberately excluded as fork-specific / deployment-specific: .gitignore (private host entries), docker-compose.prod.yml, the upstream-issues planning docs, and scripts/dev/run-next.mjs (overlaps #3301). Split out of #3073. --- .dockerignore | 13 +- README.md | 13 + docs/guides/SETUP_GUIDE.md | 9 + docs/reference/API_REFERENCE.md | 41 +++ docs/reference/CLI-TOOLS.md | 375 ++++++++++++++++++++++++ scripts/build/bootstrap-env.mjs | 19 ++ scripts/i18n/check-ui-keys-coverage.mjs | 11 +- 7 files changed, 471 insertions(+), 10 deletions(-) diff --git a/.dockerignore b/.dockerignore index bc06bf65fdbc..f0d97df70c93 100644 --- a/.dockerignore +++ b/.dockerignore @@ -39,10 +39,15 @@ playwright-report blob-report # Documentation -# Translations (~51 MB) are excluded — the Docs viewer reads English sources. -# Screenshots (~1.7 MB) and SVGs (~250 KB) are needed at build time for MDX -# image resolution (fumadocs-mdx bundles them). -# Raster sources under docs/diagrams/ only (exported SVGs are required). +# Issue #2348: The Dashboard Docs viewer reads markdown from `/app/docs` at +# runtime. The previous `docs/*` block hid every file except openapi.yaml, +# so the in-product help screen failed with ENOENT for every page. +# We now keep the English markdown tree plus the docs assets imported by MDX +# during `next build`, while still dropping the bulky translated docs and +# extra raster diagram sources that account for most of the docs footprint +# of the ~50 MB docs directory. The Docs viewer reads the default-locale +# (English) sources at runtime, so translations are not required in the +# container image. docs/i18n/** docs/diagrams/**/*.png docs/diagrams/**/*.jpg diff --git a/README.md b/README.md index e56197c88705..bee23a128f9d 100644 --- a/README.md +++ b/README.md @@ -523,6 +523,19 @@ curl http://localhost:20128/v1/models -H "Authorization: Bearer YOUR_KEY" You should see your connected models listed. 🎉 That's it — start coding, and OmniRoute auto-routes & falls back for you. +If your client cannot send custom headers, OmniRoute also exposes tokenized compatibility aliases: + +```txt +OpenAI catalog: http://localhost:20128/vscode/YOUR_KEY/ +OpenAI models: http://localhost:20128/vscode/YOUR_KEY/models +OpenAI chat: http://localhost:20128/vscode/YOUR_KEY/chat/completions +OpenAI responses: http://localhost:20128/vscode/YOUR_KEY/responses +Ollama chat: http://localhost:20128/vscode/YOUR_KEY/api/chat +Ollama tags: http://localhost:20128/vscode/YOUR_KEY/api/tags +``` + +Use these only for clients that cannot attach `Authorization: Bearer ...`. Header auth remains the preferred mode. +
## 📦 More install methods — Docker, source, pnpm, Arch diff --git a/docs/guides/SETUP_GUIDE.md b/docs/guides/SETUP_GUIDE.md index 04c1e29d72f1..1946fd8e23f6 100644 --- a/docs/guides/SETUP_GUIDE.md +++ b/docs/guides/SETUP_GUIDE.md @@ -153,6 +153,15 @@ API Key: [copy from Endpoint page] Model: if/kimi-k2-thinking (or any provider/model prefix) ``` +If your editor cannot send `Authorization: Bearer ...`, use the tokenized compatibility base instead: + +```txt +Base URL: http://localhost:20128/api/v1/vscode/YOUR_KEY/ +Models URL: http://localhost:20128/api/v1/vscode/YOUR_KEY/models +Chat URL: http://localhost:20128/api/v1/vscode/YOUR_KEY/chat/completions +Ollama Tags URL: http://localhost:20128/api/v1/vscode/YOUR_KEY/api/tags +``` + Works with Claude Code, Codex CLI, Gemini CLI, Cursor, Cline, OpenClaw, OpenCode, and OpenAI-compatible SDKs. For detailed per-tool configuration (Claude Code, Codex CLI, Cursor, Cline, OpenClaw, Kilo Code, Copilot, and more), see the dedicated **[CLI Tools Guide](../reference/CLI-TOOLS.md)**. diff --git a/docs/reference/API_REFERENCE.md b/docs/reference/API_REFERENCE.md index 43c6a71e7f03..dc5712fc42a6 100644 --- a/docs/reference/API_REFERENCE.md +++ b/docs/reference/API_REFERENCE.md @@ -154,9 +154,17 @@ Authorization: Bearer your-api-key | GET | `/v1beta/models` | Gemini | | POST | `/v1beta/models/{...path}` | Gemini generateContent | | POST | `/v1/api/chat` | Ollama | +| GET | `/api/v1/vscode/{token}/` | OpenAI catalog alias | +| GET | `/api/v1/vscode/{token}/models` | OpenAI models alias | +| POST | `/api/v1/vscode/{token}/chat/completions` | OpenAI tokenized alias | +| POST | `/api/v1/vscode/{token}/responses` | OpenAI Responses tokenized alias | +| POST | `/api/v1/vscode/{token}/api/chat` | Ollama tokenized alias | +| GET | `/api/v1/vscode/{token}/api/tags` | Ollama tags tokenized alias | All POST routes follow the same shape: `Bearer your-api-key` + Zod-validated JSON body (`v1RerankSchema`, `v1ModerationSchema`, `v1AudioSpeechSchema`, etc., see `src/shared/validation/schemas.ts`). 4xx is returned on schema failure. +For clients that cannot attach `Authorization: Bearer ...`, OmniRoute also accepts API keys in the URL via either query-string compatibility (`?token=...`, `?apiKey=...`, `?api_key=...`, `?key=...`) or the dedicated `/api/v1/vscode/{token}/...` endpoints documented below. + ```bash # Rerank POST /v1/rerank { "model": "cohere/rerank-3", "query": "...", "documents": ["..."] } @@ -608,6 +616,39 @@ GET /api/tags Requests are automatically translated between Ollama and internal formats. +## Tokenized VS Code / Headerless Aliases + +Use these aliases when an integration cannot inject an `Authorization` header and needs the API key embedded in the base URL. + +```bash +# OpenAI-style catalog alias +GET /api/v1/vscode/{token}/ +GET /api/v1/vscode/{token}/models + +# OpenAI-style chat aliases +POST /api/v1/vscode/{token}/chat/completions +POST /api/v1/vscode/{token}/responses + +# Ollama-style aliases +POST /api/v1/vscode/{token}/api/chat +GET /api/v1/vscode/{token}/api/tags +``` + +Example: + +```bash +curl https://your-host.example/api/v1/vscode/YOUR_API_KEY/models +curl -X POST https://your-host.example/api/v1/vscode/YOUR_API_KEY/chat/completions \ + -H "Content-Type: application/json" \ + -d '{"model":"auto","messages":[{"role":"user","content":"hello"}]}' +``` + +Notes: + +- The tokenized aliases reuse the same handlers as `/v1/*` and `/api/tags`; response shapes stay identical. +- Prefer `Authorization: Bearer ...` whenever the client supports custom headers. +- URL-based tokens may appear in reverse-proxy logs, browser history, and telemetry outside OmniRoute. Treat them as a compatibility option, not the default authentication mode. + --- ## Telemetry diff --git a/docs/reference/CLI-TOOLS.md b/docs/reference/CLI-TOOLS.md index 24e2a199b665..352693013359 100644 --- a/docs/reference/CLI-TOOLS.md +++ b/docs/reference/CLI-TOOLS.md @@ -297,6 +297,243 @@ export OPENAI_BASE_URL="http://localhost:20128/v1" export OPENAI_API_KEY="sk-your-omniroute-key" export ANTHROPIC_BASE_URL="http://localhost:20128" export ANTHROPIC_AUTH_TOKEN="sk-your-omniroute-key" +export GEMINI_BASE_URL="http://localhost:20128/v1" +export GEMINI_API_KEY="sk-your-omniroute-key" +``` + +> For a **remote server** replace `localhost:20128` with the server IP or domain, +> e.g. `http://:20128`. + +--- + +### Step 4 — Configure Each Tool + +#### Claude Code + +```bash +# Create ~/.claude/settings.json: +mkdir -p ~/.claude && cat > ~/.claude/settings.json << EOF +{ + "env": { + "ANTHROPIC_BASE_URL": "http://localhost:20128", + "ANTHROPIC_AUTH_TOKEN": "sk-your-omniroute-key" + } +} +EOF +``` + +Use the unified Anthropic gateway root for Claude Code. Do not append `/v1` here. + +**Test:** `claude "say hello"` + +--- + +#### OpenAI Codex + +```bash +mkdir -p ~/.codex && cat > ~/.codex/config.yaml << EOF +model: auto +apiKey: sk-your-omniroute-key +apiBaseUrl: http://localhost:20128/v1 +EOF +``` + +**Test:** `codex "what is 2+2?"` + +--- + +#### OpenCode + +```bash +mkdir -p ~/.config/opencode && cat > ~/.config/opencode/opencode.json << EOF +{ + "\$schema": "https://opencode.ai/config.json", + "provider": { + "omniroute": { + "npm": "@ai-sdk/openai-compatible", + "name": "OmniRoute", + "options": { + "baseURL": "http://localhost:20128/v1", + "apiKey": "sk-your-omniroute-key" + }, + "models": { + "claude-sonnet-4-5": { "name": "claude-sonnet-4-5" }, + "claude-sonnet-4-5-thinking": { "name": "claude-sonnet-4-5-thinking" }, + "gemini-3-flash": { "name": "gemini-3-flash" } + } + } + } +} +EOF +``` + +**Test:** `opencode` + +> Use `opencode run "your prompt" --model omniroute/claude-sonnet-4-5-thinking --variant high` +> to send thinking variants. + +--- + +#### Cline (CLI or VS Code) + +**CLI mode:** + +```bash +mkdir -p ~/.cline/data && cat > ~/.cline/data/globalState.json << EOF +{ + "apiProvider": "openai", + "openAiBaseUrl": "http://localhost:20128/v1", + "openAiApiKey": "sk-your-omniroute-key" +} +EOF +``` + +**VS Code mode:** +Cline extension settings → API Provider: `OpenAI Compatible` → Base URL: `http://localhost:20128/v1` + +Or use the OmniRoute dashboard → **CLI Tools → Cline → Apply Config**. + +--- + +#### KiloCode (CLI or VS Code) + +**CLI mode:** + +```bash +kilocode --api-base http://localhost:20128/v1 --api-key sk-your-omniroute-key +``` + +**VS Code settings:** + +```json +{ + "kilo-code.openAiBaseUrl": "http://localhost:20128/v1", + "kilo-code.apiKey": "sk-your-omniroute-key" +} +``` + +Or use the OmniRoute dashboard → **CLI Tools → KiloCode → Apply Config**. + +--- + +#### Continue (VS Code Extension) + +Edit `~/.continue/config.yaml`: + +```yaml +models: + - name: OmniRoute + provider: openai + model: auto + apiBase: http://localhost:20128/v1 + apiKey: sk-your-omniroute-key + default: true +``` + +Restart VS Code after editing. + +--- + +#### VS Code Insiders (`chatLanguageModels.json`) + +Use this when VS Code Insiders is configured for custom endpoint models and you want OmniRoute to work without a custom header field. + +**Recommended location:** + +- Linux: `~/.config/Code - Insiders/User/chatLanguageModels.json` +- Windows: `%APPDATA%/Code - Insiders/User/chatLanguageModels.json` + +**Example using the tokenized OmniRoute alias:** + +```json +[ + { + "vendor": "customendpoint", + "id": "auto", + "name": "OmniRoute Auto", + "family": "gpt-4", + "version": "1.0.0", + "url": "http://localhost:20128/api/v1/vscode/sk-your-omniroute-key/chat/completions", + "modelsUrl": "http://localhost:20128/api/v1/vscode/sk-your-omniroute-key/models", + "requestFormat": "openai-chat-completions", + "contextWindow": 256000, + "maxOutputTokens": 32768, + "auth": { + "type": "none" + } + } +] +``` + +**Notes:** + +- Replace `sk-your-omniroute-key` with an API key created in OmniRoute. +- The `url` field should point to `/api/v1/vscode/{token}/chat/completions`. +- The `modelsUrl` field should point to `/api/v1/vscode/{token}/models`. +- Prefer the normal `/v1` + Bearer header flow when the client supports custom headers. +- URL-embedded tokens are a compatibility fallback and may appear in editor logs or proxy history. + +--- + +#### Kiro CLI (Amazon) + +```bash +# Login to your AWS/Kiro account: +kiro-cli login + +# The CLI uses its own auth — OmniRoute is not needed as backend for Kiro CLI itself. +# Use kiro-cli alongside OmniRoute for other tools. +kiro-cli status +``` + +For the **Kiro IDE** desktop app, use the MITM endpoint exposed by OmniRoute +under `/dashboard/cli-tools → Kiro`. + +--- + +#### Qwen Code (Alibaba) + +Qwen Code supports OpenAI-compatible API endpoints via environment variables or `settings.json`. + +> Qwen OAuth free tier was discontinued on 2026-04-15. Use OmniRoute with +> `bailian-coding-plan` / `alibaba` / `alibaba-cn` / `openrouter` / `anthropic` / +> `gemini` providers instead. + +**Option 1: Environment variables (`~/.qwen/.env`)** + +```bash +mkdir -p ~/.qwen && cat > ~/.qwen/.env << EOF +OPENAI_API_KEY="sk-your-omniroute-key" +OPENAI_BASE_URL="http://localhost:20128/v1" +OPENAI_MODEL="auto" +EOF +``` + +**Option 2: `settings.json` with `security.auth`** + +```json +// ~/.qwen/settings.json +{ + "security": { + "auth": { + "selectedType": "openai", + "apiKey": "sk-your-omniroute-key", + "baseUrl": "http://localhost:20128/v1" + } + }, + "model": { + "name": "claude-sonnet-4-6" + } +} +``` + +**Option 3: Inline CLI flags** + +```bash +OPENAI_BASE_URL="http://localhost:20128/v1" \ +OPENAI_API_KEY="sk-your-omniroute-key" \ +OPENAI_MODEL="auto" \ +qwen ``` > For a **remote server** replace `localhost:20128` with the server IP or domain. @@ -320,6 +557,144 @@ omniroute --version # Print version omniroute --help # Show all commands ``` +### Setup & Initialization + +```bash +omniroute setup # Interactive setup wizard +omniroute setup --non-interactive # CI/automation mode (reads env vars + flags) +omniroute setup --password '' # Set admin password directly +omniroute setup --add-provider \ + --provider openai \ + --api-key '' \ + --test-provider # Add and test a provider in one shot +``` + +Recognized environment variables for non-interactive setup: + +| Var | Purpose | +| ----------------------------- | -------------------------------------------- | +| `OMNIROUTE_SETUP_PASSWORD` | Admin password (>=8 chars) | +| `OMNIROUTE_PROVIDER` | Provider id (e.g. `openai`, `anthropic`) | +| `OMNIROUTE_PROVIDER_NAME` | Display name for the connection | +| `OMNIROUTE_PROVIDER_BASE_URL` | Optional OpenAI-compatible base URL override | +| `OMNIROUTE_API_KEY` | Provider API key | +| `OMNIROUTE_DEFAULT_MODEL` | Optional default model | +| `DATA_DIR` | Override the OmniRoute data directory | + +### Diagnostics + +```bash +omniroute doctor # Check config, DB, ports, runtime, memory, liveness +omniroute doctor --json # Machine-readable JSON +omniroute doctor --no-liveness # Skip the HTTP health probe +omniroute doctor --host 0.0.0.0 # Override liveness host +omniroute doctor --liveness-url # Full health endpoint URL override +``` + +The doctor runs these checks: `Config`, `Database`, `Storage/encryption`, +`Port availability`, `Node runtime`, `Native binary` (better-sqlite3), +`Memory`, and `Server liveness`. It exits non-zero if any check is `fail`. + +### Provider Management + +```bash +omniroute providers available # OmniRoute provider catalog +omniroute providers available --search openai # Filter catalog by id/name/alias/category +omniroute providers available --category api-key # Filter by category (api-key, oauth, free, ...) +omniroute providers available --json # Machine-readable JSON + +omniroute providers list # Configured provider connections +omniroute providers list --json + +omniroute providers test # Test one configured connection +omniroute providers test-all # Test every active connection +omniroute providers validate # Local-only structural validation +``` + +> `providers available` reads the OmniRoute catalog; `providers list/test/test-all/validate` +> read the local SQLite database directly and do not require the server to be running. + +### Recovery & Reset + +```bash +omniroute reset-password # Reset the admin password (legacy alias still works) +omniroute reset-encrypted-columns # Show warning + dry-run for encrypted credential reset +omniroute reset-encrypted-columns --force # Actually null out encrypted credentials in SQLite +``` + +### Other subcommands + +These assume a running OmniRoute server, unless noted otherwise: + +```bash +omniroute status # Comprehensive runtime status +omniroute logs # Stream request logs (--json, --search, --follow) +omniroute config show # Display current configuration + +omniroute provider list # List available providers (alias of providers list) +omniroute provider add # Register OmniRoute as a provider on a tool +omniroute keys add | list | remove # Manage API keys +omniroute models [provider] # List models (--json, --search) +omniroute combo list | switch | create | delete + +omniroute backup # Snapshot config + DB +omniroute restore # Restore from a previous snapshot + +omniroute health # Detailed health (breakers, cache, memory) +omniroute quota # Provider quota usage +omniroute cache # Cache status +omniroute cache clear # Clear semantic + signature caches + +omniroute mcp status | restart # MCP server status / restart +omniroute a2a status | card # A2A server status / agent card + +omniroute tunnel list | create | stop # Manage tunnels (cloudflare/tailscale/ngrok) +omniroute env show | get | set # Inspect / set env vars (temporary) + +omniroute test # Provider connectivity smoke test +omniroute update # Check for updates +omniroute completion # Generate shell completion +``` + +### Common flags + +| Flag | Description | +| ------------------- | ------------------------------------------------------ | +| `--no-open` | Don't auto-open the browser on start | +| `--port ` | Override the API port (default 20128) | +| `--mcp` | Run as MCP server over stdio (for IDEs) | +| `--non-interactive` | CI mode (no prompts; reads from env/flags) | +| `--json` | Machine-readable JSON output (doctor, providers, etc.) | +| `--help`, `-h` | Show command-specific help | +| `--version`, `-v` | Print the installed version | + +--- + +## Available API Endpoints + +| Endpoint | Description | Use For | +| -------------------------- | ----------------------------- | --------------------------- | +| `/v1/chat/completions` | Standard chat (all providers) | All modern tools | +| `/v1/responses` | Responses API (OpenAI format) | Codex, agentic workflows | +| `/v1/completions` | Legacy text completions | Older tools using `prompt:` | +| `/v1/embeddings` | Text embeddings | RAG, search | +| `/v1/images/generations` | Image generation | GPT-Image, Flux, etc. | +| `/v1/audio/speech` | Text-to-speech | ElevenLabs, OpenAI TTS | +| `/v1/audio/transcriptions` | Speech-to-text | Deepgram, AssemblyAI | + +Ready-to-paste examples with a tokenized OmniRoute URL: + +```txt +Token example: sk-a3ab3c080beaee3a-69f4a4-070d71af + +Standard OpenAI base: http://localhost:20128/v1 +VS Code models: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/models +VS Code chat: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/chat/completions +VS Code responses: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/responses +Ollama tags: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/api/tags +Ollama chat: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/api/chat +``` + --- ## Troubleshooting diff --git a/scripts/build/bootstrap-env.mjs b/scripts/build/bootstrap-env.mjs index c39e9b910751..39dd8b415953 100644 --- a/scripts/build/bootstrap-env.mjs +++ b/scripts/build/bootstrap-env.mjs @@ -67,6 +67,21 @@ function getPreferredEnvFilePath(env = process.env) { return candidates.find((filePath) => existsSync(filePath)) ?? null; } +function isNativeSqliteLoadError(error) { + const message = error instanceof Error ? error.message : String(error); + const code = error && typeof error === "object" && "code" in error ? error.code : undefined; + + return ( + message.includes("Module did not self-register") || + message.includes("NODE_MODULE_VERSION") || + message.includes("ERR_DLOPEN_FAILED") || + message.includes("Could not locate the bindings file") || + message.includes("Cannot find module 'better-sqlite3'") || + code === "ERR_DLOPEN_FAILED" || + code === "MODULE_NOT_FOUND" + ); +} + function hasEncryptedCredentials(dataDir) { const dbPath = join(dataDir, "storage.sqlite"); if (!existsSync(dbPath)) return false; @@ -91,6 +106,10 @@ function hasEncryptedCredentials(dataDir) { db.close(); } } catch (error) { + if (isNativeSqliteLoadError(error)) { + return false; + } + const message = error instanceof Error ? error.message : String(error); throw new Error(`Unable to inspect existing database at ${dbPath}: ${message}`); } diff --git a/scripts/i18n/check-ui-keys-coverage.mjs b/scripts/i18n/check-ui-keys-coverage.mjs index 24be2aa9e588..db4dde8a1050 100644 --- a/scripts/i18n/check-ui-keys-coverage.mjs +++ b/scripts/i18n/check-ui-keys-coverage.mjs @@ -76,10 +76,10 @@ function isPlainObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value); } -function collectLeafPaths(obj, prefix = "") { +function collectLeafPaths(obj, prefix = []) { const paths = []; for (const [key, value] of Object.entries(obj)) { - const next = prefix ? `${prefix}.${key}` : key; + const next = [...prefix, key]; if (isPlainObject(value)) { paths.push(...collectLeafPaths(value, next)); } else { @@ -94,8 +94,7 @@ function collectLeafPaths(obj, prefix = "") { // scanner correctly flags any dynamic indexing with untrusted-looking keys. const FORBIDDEN_KEY_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]); -function lookupPath(obj, dotPath) { - const parts = dotPath.split("."); +function lookupPath(obj, parts) { let cur = obj; for (const part of parts) { if (!isPlainObject(cur)) return undefined; @@ -170,8 +169,8 @@ async function main() { let present = 0; let missing = 0; let placeholder = 0; - for (const dotPath of enPaths) { - const value = lookupPath(target, dotPath); + for (const pathParts of enPaths) { + const value = lookupPath(target, pathParts); if (value === undefined) { missing++; continue;