From 4d460109dd946293125af7e7455042df2e7edd8d Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sat, 11 Apr 2026 18:00:20 -0300 Subject: [PATCH] =?UTF-8?q?chore(release):=20v3.6.3=20=E2=80=94=20Fix=20cl?= =?UTF-8?q?oudflare=20config,=20prompt=20cache=20payloads,=20and=20openai-?= =?UTF-8?q?compatible=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 630 +++++++++++++++--- CHANGELOG.md | 12 + docs/openapi.yaml | 2 +- electron/package.json | 2 +- eslint.config.mjs | 1 + open-sse/executors/vertex.ts | 4 +- open-sse/handlers/chatCore.ts | 1 + open-sse/package.json | 2 +- open-sse/services/errorClassifier.ts | 6 +- package-lock.json | 2 +- scratch.mjs | 12 + .../dashboard/providers/[id]/page.tsx | 40 +- src/i18n/messages/ar.json | 2 +- src/i18n/messages/bg.json | 2 +- src/i18n/messages/cs.json | 2 +- src/i18n/messages/da.json | 2 +- src/i18n/messages/de.json | 2 +- src/i18n/messages/en.json | 2 +- src/i18n/messages/es.json | 2 +- src/i18n/messages/fi.json | 2 +- src/i18n/messages/fr.json | 2 +- src/i18n/messages/he.json | 2 +- src/i18n/messages/hi.json | 2 +- src/i18n/messages/hu.json | 2 +- src/i18n/messages/id.json | 2 +- src/i18n/messages/it.json | 2 +- src/i18n/messages/ja.json | 2 +- src/i18n/messages/ko.json | 2 +- src/i18n/messages/ms.json | 2 +- src/i18n/messages/nl.json | 2 +- src/i18n/messages/no.json | 2 +- src/i18n/messages/phi.json | 2 +- src/i18n/messages/pl.json | 2 +- src/i18n/messages/pt-BR.json | 2 +- src/i18n/messages/pt.json | 2 +- src/i18n/messages/ro.json | 2 +- src/i18n/messages/ru.json | 2 +- src/i18n/messages/sk.json | 2 +- src/i18n/messages/sv.json | 2 +- src/i18n/messages/th.json | 2 +- src/i18n/messages/tr.json | 2 +- src/i18n/messages/uk-UA.json | 2 +- src/i18n/messages/vi.json | 2 +- src/i18n/messages/zh-CN.json | 2 +- src/lib/providers/validation.ts | 12 + src/shared/validation/schemas.ts | 4 +- tests/unit/cc-compatible-provider.test.mjs | 2 +- 47 files changed, 649 insertions(+), 147 deletions(-) create mode 100644 scratch.mjs diff --git a/.env.example b/.env.example index ee027a72e3..4e12f640a3 100644 --- a/.env.example +++ b/.env.example @@ -1,114 +1,313 @@ -# OmniRoute environment contract -# This file reflects actual runtime usage in the current codebase. +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ OmniRoute — .env Contract │ +# │ This file documents EVERY environment variable read by the runtime. │ +# │ Copy to .env and adjust values. Lines starting with # are commented out │ +# │ (optional / off-by-default). Uncomment only what you need. │ +# │ Reference: docs/ENVIRONMENT.md for full details and usage scenarios. │ +# └─────────────────────────────────────────────────────────────────────────────┘ -# ═══════════════════════════════════════════════════ -# REQUIRED SECRETS — Generate strong values! -# ═══════════════════════════════════════════════════ -# Generate with: openssl rand -base64 48 + +# ═══════════════════════════════════════════════════════════════════════════════ +# 1. REQUIRED SECRETS — Must be set before first run! +# ═══════════════════════════════════════════════════════════════════════════════ +# These secrets are critical for security. Generate strong, unique values. + +# JWT signing key for dashboard session tokens. +# Used by: src/lib/auth — signs/verifies all authenticated session cookies. +# Generate: openssl rand -base64 48 JWT_SECRET= -# Generate with: openssl rand -hex 32 + +# Encryption key for API keys stored in the database. +# Used by: src/lib/db/apiKeys.ts — encrypts API key values at rest in SQLite. +# Generate: openssl rand -hex 32 API_KEY_SECRET= -# Initial admin password — CHANGE THIS before first use! +# Initial admin login password — CHANGE THIS before first use! +# Used by: bootstrap only — sets the initial dashboard password on first boot. +# After first login you can change it from Dashboard → Settings → Security. +# Default: 123456 (insecure, for local dev only) INITIAL_PASSWORD=123456 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 2. STORAGE & DATABASE +# ═══════════════════════════════════════════════════════════════════════════════ +# OmniRoute uses SQLite for all persistence. These variables control where +# data lives, encryption, and cleanup policies. + +# Base directory for all persistent data (SQLite DB, logs, backups). +# Used by: src/lib/db/core.ts — resolves the SQLite database file path. +# Default: ~/.omniroute/ | Override for Docker or custom installations. # DATA_DIR=/var/lib/omniroute -# Storage (SQLite) -STORAGE_DRIVER=sqlite -# Generate with: openssl rand -hex 32 +# Encryption key for SQLite database encryption at rest. +# Used by: src/lib/db/encryption.ts — encrypts the entire SQLite database. +# Generate: openssl rand -hex 32 | Leave empty to disable DB encryption. STORAGE_ENCRYPTION_KEY= + +# Version tag for the encryption key — allows future key rotation. +# Used by: scripts/bootstrap-env.mjs, electron/main.js — persists key version. +# Default: v1 | Increment when rotating STORAGE_ENCRYPTION_KEY. STORAGE_ENCRYPTION_KEY_VERSION=v1 -APP_LOG_RETENTION_DAYS=90 -CALL_LOG_RETENTION_DAYS=90 -SQLITE_MAX_SIZE_MB=2048 -SQLITE_CLEAN_LEGACY_FILES=true + +# Automatic SQLite backup on startup. +# Used by: src/lib/db/backup.ts — creates a timestamped backup before migrations. +# Default: false (backups enabled) | Set true to skip backup on every restart. DISABLE_SQLITE_AUTO_BACKUP=false -# Recommended runtime variables -# Canonical/base port (keeps backward compatibility) + +# ═══════════════════════════════════════════════════════════════════════════════ +# 3. NETWORK & PORTS +# ═══════════════════════════════════════════════════════════════════════════════ +# OmniRoute can run on a single port (default) or split Dashboard/API ports. + +# Canonical port for both Dashboard UI and API (single-port mode). +# Used by: src/lib/runtime/ports.ts — base port for the Next.js server. +# Default: 20128 PORT=20128 -# Optional split ports: + +# Split-port mode: serve Dashboard and API on separate ports for network isolation. +# Used by: src/lib/runtime/ports.ts — overrides PORT for each service. # API_PORT=20129 # API_HOST=0.0.0.0 # DASHBOARD_PORT=20128 -# Optional Docker production host publish ports: + +# Docker production port mappings (docker-compose.prod.yml only). +# These set the HOST-side published ports. Container ports use PORT/API_PORT. # PROD_DASHBOARD_PORT=20130 # PROD_API_PORT=20131 + +# Runtime override used by Electron and wrapped environments. +# OMNIROUTE_PORT takes precedence over PORT when running inside wrappers. +# Used by: src/lib/runtime/ports.ts — preserves canonical port in Electron. +# OMNIROUTE_PORT=20128 + +# Environment mode — affects Next.js behavior, logging verbosity, and caching. +# Values: production | development | Default: production NODE_ENV=production -INSTANCE_NAME=omniroute -# Recommended security and ops variables + +# ═══════════════════════════════════════════════════════════════════════════════ +# 4. SECURITY & AUTHENTICATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Salt for generating unique machine IDs (fingerprint diversification). +# Used by: src/lib/auth — combined with hardware identifiers for machine-id hash. +# Default: endpoint-proxy-salt | Change per-deployment for isolation. MACHINE_ID_SALT=endpoint-proxy-salt -AUTH_COOKIE_SECURE=false -REQUIRE_API_KEY=false -ALLOW_API_KEY_REVEAL=false -PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70 -# Input Sanitizer (FASE-01 — prompt injection & PII protection) +# Set true when running behind HTTPS (reverse proxy with TLS termination). +# Used by: src/lib/auth — sets the Secure flag on session cookies. +# Default: false | MUST be true in any non-localhost deployment. +AUTH_COOKIE_SECURE=false + +# Require an API key for all /v1/* proxy endpoints. +# Used by: API middleware — rejects unauthenticated requests to the proxy API. +# Default: false | Set true for multi-user/public deployments. +REQUIRE_API_KEY=false + +# Allow revealing full API key values in the Dashboard UI. +# Used by: Dashboard providers page — controls show/hide of key values. +# Default: false | Security risk if enabled on shared instances. +ALLOW_API_KEY_REVEAL=false + +# Comma-separated API key IDs that skip request logging (GDPR/compliance). +# Used by: src/lib/compliance/index.ts — suppresses logs for specific keys. +# NO_LOG_API_KEY_IDS=key_abc123,key_def456 + +# Maximum request body size in bytes (rejects larger payloads). +# Used by: src/shared/middleware/bodySizeGuard.ts — prevents oversized uploads. +# Default: 10485760 (10 MB) +# MAX_BODY_SIZE_BYTES=10485760 + +# CORS configuration — controls which origins can call the API. +# Used by: Next.js middleware — sets Access-Control-Allow-Origin header. +# Default: * (all origins) | Restrict for production security. +# CORS_ORIGIN=https://your-domain.com + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 5. INPUT SANITIZATION & PII PROTECTION (FASE-01) +# ═══════════════════════════════════════════════════════════════════════════════ +# Multi-layer defense: request-side injection guard + response-side PII sanitizer. + +# ── Request-Side: Prompt Injection Guard ── +# Scans incoming messages for prompt injection patterns before routing. +# Used by: src/middleware/promptInjectionGuard.ts # INPUT_SANITIZER_ENABLED=true -# INPUT_SANITIZER_MODE=warn # warn | block | redact +# INPUT_SANITIZER_MODE=warn # warn = log only | block = reject request | redact = strip patterns + +# Legacy alias for INPUT_SANITIZER_MODE (same effect). +# INJECTION_GUARD_MODE=warn + +# PII detection in incoming requests (emails, phone numbers, SSNs, etc.). +# Used by: src/middleware/promptInjectionGuard.ts — extends injection guard. # PII_REDACTION_ENABLED=false -# Cloud sync variables -# Must point to this running instance so internal sync jobs can call /api/sync/cloud. -# Server-side preferred variables: +# ── Response-Side: PII Sanitizer ── +# Scans LLM responses for leaked PII before returning to the client. +# Used by: src/lib/piiSanitizer.ts +# PII_RESPONSE_SANITIZATION=false +# PII_RESPONSE_SANITIZATION_MODE=redact # redact = mask PII | warn = log only | block = drop response + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 6. TOOL & ROUTING POLICIES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Tool policy mode — controls which tools LLMs can invoke via function calling. +# Used by: src/lib/toolPolicy.ts — enforces allowlist/denylist on tool_choice. +# Values: allowlist | denylist | disabled | Default: disabled +# TOOL_POLICY_MODE=disabled + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 7. URLS & CLOUD SYNC +# ═══════════════════════════════════════════════════════════════════════════════ +# URLs used for internal sync jobs, OAuth callbacks, and cloud relay. + +# Internal base URL — used by server-side sync jobs to call /api/sync/cloud. +# Used by: src/lib/cloudSync.ts, src/lib/initCloudSync.ts +# Default: http://localhost:20128 BASE_URL=http://localhost:20128 + +# Cloud relay URL — premium feature for remote config sync. +# Used by: src/lib/cloudSync.ts — pushes/pulls settings from OmniRoute Cloud. CLOUD_URL= -# Backward-compatible/public variables: -# NEXT_PUBLIC_BASE_URL is also used as the OAuth redirect_uri origin when running behind a -# reverse proxy (e.g., nginx). Set this to your public-facing URL so OAuth callbacks work. -# Example: NEXT_PUBLIC_BASE_URL=https://omniroute.example.com + +# Timeout for cloud sync HTTP requests in milliseconds. +# Used by: src/lib/cloudSync.ts — fetchWithTimeout wrapper. +# Default: 12000 (12 seconds) +# CLOUD_SYNC_TIMEOUT_MS=12000 + +# Public-facing base URL — CRITICAL for reverse proxy / OAuth callback setups. +# Used by: OAuth redirect_uri computation, Dashboard UI links, cloud/model sync. +# Set to your public URL when behind nginx/Caddy (e.g., https://omniroute.example.com). +# Default: http://localhost:20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 + +# Public cloud URL — client-side mirror of CLOUD_URL. NEXT_PUBLIC_CLOUD_URL= -# Optional outbound proxy variables for upstream provider calls -# Lowercase variants are also supported: http_proxy, https_proxy, all_proxy, no_proxy -# SOCKS5 proxy support +# Legacy alias — fallback for NEXT_PUBLIC_BASE_URL in sync schedulers. +# NEXT_PUBLIC_APP_URL=http://localhost:20128 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 8. OUTBOUND PROXY (Upstream Provider Calls) +# ═══════════════════════════════════════════════════════════════════════════════ +# Route upstream LLM API calls through an HTTP/SOCKS5 proxy. +# Useful for corporate egress, geo-routing, or IP masking. + +# Enable SOCKS5 proxy support in both server and client components. +# Used by: open-sse/executors — wraps fetch() calls through the proxy agent. ENABLE_SOCKS5_PROXY=true NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true + +# Standard proxy variables (lowercase variants also supported). # HTTP_PROXY=http://127.0.0.1:7890 # HTTPS_PROXY=http://127.0.0.1:7890 # ALL_PROXY=socks5://127.0.0.1:7890 # NO_PROXY=localhost,127.0.0.1 -# TLS fingerprint spoofing (opt-in) — mimics Chrome 124 TLS handshake via wreq-js -# Reduces risk of JA3/JA4 fingerprint-based blocking by providers (e.g., Google) -# Requires wreq-js to be installed (included in dependencies) +# TLS fingerprint spoofing (opt-in) — mimics Chrome 124 TLS handshake via wreq-js. +# Reduces risk of JA3/JA4 fingerprint-based blocking by providers (e.g., Google). +# Used by: open-sse/executors — replaces Node.js default TLS fingerprint. # ENABLE_TLS_FINGERPRINT=true -# Optional CLI runtime overrides (Docker/host integration) + +# ═══════════════════════════════════════════════════════════════════════════════ +# 9. CLI TOOL INTEGRATION +# ═══════════════════════════════════════════════════════════════════════════════ +# Control how OmniRoute discovers and launches CLI sidecars (Claude, Codex, etc.). +# Used by: src/shared/services/cliRuntime.ts + +# CLI discovery mode: auto = search PATH | manual = use explicit paths below. # CLI_MODE=auto -# CLI_EXTRA_PATHS=/host-cli/bin + +# Additional PATH entries for finding CLI binaries (colon-separated). +# CLI_EXTRA_PATHS=/host-cli/bin:/usr/local/bin + +# Home directory override for reading CLI config files (~/.claude, etc.). # CLI_CONFIG_HOME=/root + +# Allow OmniRoute to write CLI config files (token refresh, etc.). # CLI_ALLOW_CONFIG_WRITES=true + +# Override binary paths for individual CLI tools. # CLI_CLAUDE_BIN=claude # CLI_CODEX_BIN=codex # CLI_DROID_BIN=droid # CLI_OPENCLAW_BIN=openclaw # CLI_CURSOR_BIN=agent # CLI_CLINE_BIN=cline -# CLI_ROO_BIN=roo # CLI_CONTINUE_BIN=cn +# CLI_QODER_BIN=qoder -# Internal agent / tool integrations (optional) -# Used by the MCP server, A2A skills, and CLI sidecars when they need to call -# the running OmniRoute instance explicitly instead of relying on localhost. + +# ═══════════════════════════════════════════════════════════════════════════════ +# 10. INTERNAL AGENT & MCP INTEGRATIONS +# ═══════════════════════════════════════════════════════════════════════════════ +# Used by MCP server, A2A skills, and CLI sidecars to call the running instance. + +# Explicit base URL for MCP/A2A tools to reach OmniRoute (overrides localhost auto-detect). +# Used by: open-sse/mcp-server/server.ts, src/lib/a2a/ # OMNIROUTE_BASE_URL=http://localhost:20128 + +# API key for internal tool calls (MCP tools, A2A skills). # OMNIROUTE_API_KEY= + +# API key ID for MCP audit logging. +# Used by: open-sse/mcp-server/audit.ts — tags audit events with a key identity. +# OMNIROUTE_API_KEY_ID= + +# Legacy alias for OMNIROUTE_API_KEY. # ROUTER_API_KEY= + +# Enforce scope-based access control on MCP tool calls. +# Used by: open-sse/mcp-server/server.ts — rejects calls outside allowed scopes. +# OMNIROUTE_MCP_ENFORCE_SCOPES=false + +# Comma-separated scopes granted to this MCP connection. +# Full list: admin, combos, health, models, routing, budget, metrics, pricing, memory, skills +# OMNIROUTE_MCP_SCOPES=admin,combos,health + +# Model catalog sync interval in hours. +# Used by: src/shared/services/modelSyncScheduler.ts — periodic model refresh. +# Default: 24 # MODEL_SYNC_INTERVAL_HOURS=24 -# ═══════════════════════════════════════════════════ -# OAUTH PROVIDER CREDENTIALS -# ═══════════════════════════════════════════════════ -# These are the built-in default credentials that work for localhost setups. -# For remote/VPS deployments, register your own credentials at each provider. -# The sync-env script will auto-populate these in your .env if missing. -# -# These can also be overridden via data/provider-credentials.json where supported. +# Provider limits sync interval in minutes (rate limit windows, quotas). +# Used by: src/server-init.ts — polls provider health endpoints. +# Default: 70 +PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70 + +# Disable all background services (sync, pricing, model refresh). +# Used by: src/instrumentation-node.ts, src/lib/initCloudSync.ts +# Useful for: CI builds, test environments, or resource-constrained containers. +# OMNIROUTE_DISABLE_BACKGROUND_SERVICES=false + +# Flag set by bootstrap script after initial setup is complete. +# Used by: src/app/(dashboard)/dashboard/page.tsx — shows setup wizard vs. dashboard. +# OMNIROUTE_BOOTSTRAPPED=false + +# Allow request body to override the Antigravity project field. +# Used by: open-sse/executors/antigravity.ts — escape hatch for multi-project setups. +# OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE=0 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 11. OAUTH PROVIDER CREDENTIALS +# ═══════════════════════════════════════════════════════════════════════════════ +# Built-in default credentials for localhost development. +# For remote/VPS deployments, register your own at each provider's developer console. +# The bootstrap-env script auto-populates these in .env if missing. +# Can also be overridden via data/provider-credentials.json where supported. # ── Claude Code (Anthropic) ── CLAUDE_OAUTH_CLIENT_ID=9d1c250a-e61b-44d9-88ed-5944d1962f5e +# Custom redirect URI override for Claude OAuth callback. +# CLAUDE_CODE_REDIRECT_URI=https://platform.claude.com/oauth/code/callback # ── Codex / OpenAI ── CODEX_OAUTH_CLIENT_ID=app_EMoamEEZ73f0CkXaXp7hrann @@ -143,23 +342,41 @@ QODER_OAUTH_CLIENT_SECRET=4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW # QODER_OAUTH_USERINFO_URL= # QODER_OAUTH_CLIENT_ID= +# ── Qoder Personal Access Token (direct API key fallback) ── +# Used by: open-sse/executors/qoder.ts — bypasses OAuth when set. +# QODER_PERSONAL_ACCESS_TOKEN= +# QODER_CLI_WORKSPACE= +# OMNIROUTE_QODER_WORKSPACE= + # ───────────────────────────────────────────────────────────────────────────── # ⚠️ GOOGLE OAUTH (Antigravity, Gemini CLI) — IMPORTANT FOR REMOTE SERVERS # ───────────────────────────────────────────────────────────────────────────── # The credentials above ONLY work when OmniRoute runs on localhost. -# If you are hosting OmniRoute on a remote server, register your own: +# For remote hosting: # 1. Go to https://console.cloud.google.com/apis/credentials # 2. Create an OAuth 2.0 Client ID (type: "Web application") # 3. Add your server URL as Authorized redirect URI # 4. Replace the values above with your credentials. # ───────────────────────────────────────────────────────────────────────────── -# ───────────────────────────────────────────────────────────────────────────── -# Provider User-Agent Overrides (optional — customize per-provider UA headers) -# ───────────────────────────────────────────────────────────────────────────── +# ── OAuth sidecar/CLI bridge (internal) ── +# Used by: src/lib/oauth/config/index.ts — internal CLI↔OmniRoute auth bridge. +# OMNIROUTE_SERVER=http://localhost:20128 +# OMNIROUTE_TOKEN= +# OMNIROUTE_USER_ID=cli +# CLI_TOKEN= # legacy alias for OMNIROUTE_TOKEN +# CLI_USER_ID= # legacy alias for OMNIROUTE_USER_ID +# SERVER_URL= # legacy alias for OMNIROUTE_SERVER + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 12. PROVIDER USER-AGENT OVERRIDES +# ═══════════════════════════════════════════════════════════════════════════════ +# Customize the User-Agent header sent to each upstream provider. # Format: {PROVIDER_ID}_USER_AGENT=custom-value -# When set, overrides the default User-Agent header sent to that provider. -# Useful when providers update versions or block old user-agents. +# Used by: open-sse/executors/base.ts — buildHeaders() dynamic lookup. +# Update these when providers release new CLI versions to avoid blocks. + CLAUDE_USER_AGENT=claude-cli/1.0.83 (external, cli) CODEX_USER_AGENT=codex-cli/0.92.0 (Windows 10.0.26100; x64) GITHUB_USER_AGENT=GitHubCopilotChat/0.26.7 @@ -170,13 +387,15 @@ QWEN_USER_AGENT=QwenCode/0.12.3 (linux; x64) CURSOR_USER_AGENT=connect-es/1.6.1 GEMINI_CLI_USER_AGENT=google-api-nodejs-client/9.15.1 -# ───────────────────────────────────────────────────────────────────────────── -# CLI Fingerprint Compatibility (optional — match native CLI binary signatures) -# ───────────────────────────────────────────────────────────────────────────── + +# ═══════════════════════════════════════════════════════════════════════════════ +# 13. CLI FINGERPRINT COMPATIBILITY (Anti-Detection) +# ═══════════════════════════════════════════════════════════════════════════════ # When enabled, OmniRoute reorders HTTP headers and JSON body fields to match # the exact signature of official CLI tools, reducing account flagging risk. # Your proxy IP is preserved — you get both stealth AND IP masking. -# +# Used by: open-sse/config/cliFingerprints.ts, open-sse/executors/base.ts + # Enable per-provider: # CLI_COMPAT_CODEX=1 # CLI_COMPAT_CLAUDE=1 @@ -188,12 +407,18 @@ GEMINI_CLI_USER_AGENT=google-api-nodejs-client/9.15.1 # CLI_COMPAT_KILOCODE=1 # CLI_COMPAT_CLINE=1 # CLI_COMPAT_QWEN=1 -# + # Or enable for all providers at once: # CLI_COMPAT_ALL=1 -# API Key Providers (Phase 1 + Phase 4) -# Add via Dashboard → Providers → Add API Key, or set here + +# ═══════════════════════════════════════════════════════════════════════════════ +# 14. API KEY PROVIDERS +# ═══════════════════════════════════════════════════════════════════════════════ +# API keys for direct-authentication providers. +# Preferred setup: Dashboard → Providers → Add API Key. +# Setting here is an alternative for Docker/headless deployments. + # DEEPSEEK_API_KEY= # GROQ_API_KEY= # XAI_API_KEY= @@ -207,54 +432,259 @@ GEMINI_CLI_USER_AGENT=google-api-nodejs-client/9.15.1 # Embedding Providers (optional — used by /v1/embeddings) # NEBIUS_API_KEY= -# Provider keys above (openai, mistral, together, fireworks, nvidia) also work for embeddings +# Provider keys above (OpenAI, Mistral, Together, Fireworks, NVIDIA) also work for embeddings. -# Timeout settings -# REQUEST_TIMEOUT_MS=600000 -# STREAM_IDLE_TIMEOUT_MS=600000 -# Advanced timeout overrides (optional) -# FETCH_TIMEOUT_MS=600000 -# FETCH_HEADERS_TIMEOUT_MS=600000 -# FETCH_BODY_TIMEOUT_MS=600000 -# FETCH_CONNECT_TIMEOUT_MS=30000 -# FETCH_KEEPALIVE_TIMEOUT_MS=4000 -# TLS_CLIENT_TIMEOUT_MS=600000 -# API bridge timeout for /v1 proxy requests (default: 30000) -# API_BRIDGE_PROXY_TIMEOUT_MS=600000 -# API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS=600000 -# API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS=60000 -# API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS=5000 -# API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS=0 -# CORS configuration (default: * allows all origins) -# CORS_ORIGIN=* +# ═══════════════════════════════════════════════════════════════════════════════ +# 15. TIMEOUT SETTINGS +# ═══════════════════════════════════════════════════════════════════════════════ +# All timeout values are in milliseconds. +# Used by: src/shared/utils/runtimeTimeouts.ts — centralized timeout resolution. +# +# Hierarchy: REQUEST_TIMEOUT_MS acts as a global override. +# If set, it becomes the default for FETCH_TIMEOUT_MS and STREAM_IDLE_TIMEOUT_MS. +# The fine-grained variables below override their respective defaults only when set. -# Logging +# ── Global shortcut ── +# REQUEST_TIMEOUT_MS=600000 # Overrides both fetch and stream idle defaults + +# ── Upstream fetch (provider calls) ── +# FETCH_TIMEOUT_MS=600000 # Total request timeout (default: 600000 = 10 min) +# FETCH_HEADERS_TIMEOUT_MS=600000 # Time to receive response headers +# FETCH_BODY_TIMEOUT_MS=600000 # Time to receive full response body +# FETCH_CONNECT_TIMEOUT_MS=30000 # TCP connection establishment (default: 30s) +# FETCH_KEEPALIVE_TIMEOUT_MS=4000 # Keep-alive socket idle timeout (default: 4s) + +# ── Stream idle detection ── +# STREAM_IDLE_TIMEOUT_MS=600000 # Max silence between SSE chunks (default: 600000) +# # Extended-thinking models rarely pause >90s. + +# ── TLS client (wreq-js fingerprint proxy) ── +# TLS_CLIENT_TIMEOUT_MS=600000 # Inherits from FETCH_TIMEOUT_MS by default + +# ── API Bridge (/v1 proxy server) ── +# API_BRIDGE_PROXY_TIMEOUT_MS=30000 # Proxy hop timeout (default: 30s) +# API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS=300000 # Overall server request timeout +# API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS=60000 # Time to send response headers +# API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS=5000 # Keep-alive idle timeout +# API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS=0 # Raw socket timeout (0 = disabled) + +# ── Graceful shutdown ── +# Time to wait for in-flight requests before force-exiting on SIGTERM/SIGINT. +# Used by: src/lib/gracefulShutdown.ts +# Default: 30000 (30 seconds) +# SHUTDOWN_TIMEOUT_MS=30000 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 16. LOGGING +# ═══════════════════════════════════════════════════════════════════════════════ +# Used by: src/lib/logEnv.ts, src/lib/logRotation.ts, src/shared/utils/logger.ts + +# Application log level — controls console and file log verbosity. +# Values: debug | info | warn | error | Default: info # APP_LOG_LEVEL=info + +# Log output format. +# Values: text | json | Default: text # APP_LOG_FORMAT=text + +# Write logs to file in addition to stdout. +# Default: true | Set false to disable file logging. APP_LOG_TO_FILE=true + +# Path to the application log file. +# Default: logs/application/app.log (relative to project root / DATA_DIR) # APP_LOG_FILE_PATH=logs/application/app.log + +# Maximum single log file size before rotation. +# Accepts: plain bytes or suffixed (50M, 1G, 512K). Default: 50M # APP_LOG_MAX_FILE_SIZE=50M + +# Days to keep rotated application log files before auto-deletion. +# Default: 7 # APP_LOG_RETENTION_DAYS=7 + +# Maximum number of rotated log file backups to keep. +# Default: 20 # APP_LOG_MAX_FILES=20 + +# Days to keep request/call log entries in the database before auto-cleanup. +# Default: 7 # CALL_LOG_RETENTION_DAYS=7 + +# Maximum call log entries stored in-memory buffer. +# Default: 10000 # CALL_LOG_MAX_ENTRIES=10000 -# ───────────────────────────────────────────────────────────────────────────── -# Memory Optimization (Low-RAM configurations) -# ───────────────────────────────────────────────────────────────────────────── -# Node.js heap limit in MB (default: 256 for Docker, system default for npm) +# Maximum rows in the call_logs SQLite table before oldest entries are pruned. +# Default: 100000 +# CALL_LOGS_TABLE_MAX_ROWS=100000 + +# Maximum rows in the proxy_logs SQLite table. +# Default: 100000 +# PROXY_LOGS_TABLE_MAX_ROWS=100000 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 17. MEMORY OPTIMIZATION (Low-RAM / Docker) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Node.js V8 heap limit in MB. +# Used by: Docker entrypoint — sets --max-old-space-size. +# Default: 256 (Docker) | system default (npm) # OMNIROUTE_MEMORY_MB=256 -# Prompt cache settings -# PROMPT_CACHE_MAX_SIZE=50 -# PROMPT_CACHE_MAX_BYTES=2097152 -# PROMPT_CACHE_TTL_MS=300000 +# ── Prompt cache (system prompt deduplication) ── +# Used by: open-sse/services — caches identical system prompts across requests. +# PROMPT_CACHE_MAX_SIZE=50 # Max cached entries (default: 50) +# PROMPT_CACHE_MAX_BYTES=2097152 # Max total cache size in bytes (default: 2 MB) +# PROMPT_CACHE_TTL_MS=300000 # Cache entry TTL (default: 5 minutes) -# Semantic cache settings (temperature=0 responses) -# SEMANTIC_CACHE_MAX_SIZE=100 -# SEMANTIC_CACHE_MAX_BYTES=4194304 -# SEMANTIC_CACHE_TTL_MS=1800000 +# ── Semantic cache (deterministic response dedup, temperature=0) ── +# Used by: open-sse/services — caches identical temperature=0 responses. +# SEMANTIC_CACHE_MAX_SIZE=100 # Max cached entries (default: 100) +# SEMANTIC_CACHE_MAX_BYTES=4194304 # Max total cache size in bytes (default: 4 MB) +# SEMANTIC_CACHE_TTL_MS=1800000 # Cache entry TTL (default: 30 minutes) -# In-memory log buffers +# ── In-memory log buffers ── +# Maximum recent stream events kept in memory for the Dashboard live view. # STREAM_HISTORY_MAX=50 + +# ── Context length default ── +# Global fallback max context length for models without explicit config. +# Used by: open-sse/services/contextManager.ts +# CONTEXT_LENGTH_DEFAULT=128000 + +# ── Usage token buffer ── +# Extra token headroom reserved when tracking usage quotas (prevents over-limit). +# Used by: open-sse/utils/usageTracking.ts +# USAGE_TOKEN_BUFFER=100 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 18. PRICING SYNC +# ═══════════════════════════════════════════════════════════════════════════════ +# Automatic model pricing synchronization from external sources. +# Used by: src/lib/pricingSync.ts + +# Enable periodic pricing data sync. Default: false (opt-in only). +# PRICING_SYNC_ENABLED=false + +# Sync interval in seconds. Default: 86400 (24 hours). +# PRICING_SYNC_INTERVAL=86400 + +# Comma-separated data sources. Default: litellm +# PRICING_SYNC_SOURCES=litellm + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 19. MODEL SYNC (Dev) +# ═══════════════════════════════════════════════════════════════════════════════ +# Development-time model catalog sync interval in seconds. +# Used by: src/lib/modelsDevSync.ts +# Default: 86400 (24 hours) +# MODELS_DEV_SYNC_INTERVAL=86400 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 20. PROVIDER-SPECIFIC SETTINGS +# ═══════════════════════════════════════════════════════════════════════════════ + +# ── OpenRouter ── +# OpenRouter model catalog cache TTL in ms. +# Used by: src/lib/catalog/openrouterCatalog.ts +# Default: 86400000 (24 hours) +# OPENROUTER_CATALOG_TTL_MS=86400000 + +# ── NanoBanana (Image Generation) ── +# Polling config for async image generation jobs. +# Used by: open-sse/handlers/imageGeneration.ts +# NANOBANANA_POLL_TIMEOUT_MS=120000 # Max wait for job completion (default: 120s) +# NANOBANANA_POLL_INTERVAL_MS=2500 # Poll frequency (default: 2.5s) + +# ── Cloudflare Workers AI ── +# Account ID override for Cloudflare Workers AI executor. +# Used by: open-sse/executors/cloudflare-ai.ts +# CLOUDFLARE_ACCOUNT_ID= + +# ── Cloudflare Tunnel (cloudflared) ── +# Custom path to cloudflared binary for tunnel management. +# Used by: src/lib/cloudflaredTunnel.ts +# CLOUDFLARED_BIN=/usr/local/bin/cloudflared + +# ── Search cache ── +# TTL for search API response caching (Perplexity, Brave, etc.). +# Used by: open-sse/services/searchCache.ts +# Default: 300000 (5 minutes) +# SEARCH_CACHE_TTL_MS=300000 + +# ── OpenAI-compatible multi-connection ── +# Allow multiple simultaneous connections per OpenAI-compatible provider node. +# Used by: src/app/api/providers/route.ts +# ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE=false + +# ── CC-compatible provider (experimental) ── +# Enable the Claude Code compatible provider endpoint. +# Used by: src/shared/utils/featureFlags.ts +# ENABLE_CC_COMPATIBLE_PROVIDER=false + +# ── CLIProxyAPI bridge (legacy) ── +# Connection settings for external CLIProxyAPI instances. +# Used by: open-sse/executors/cliproxyapi.ts +# CLIPROXYAPI_HOST=127.0.0.1 +# CLIPROXYAPI_PORT=5544 +# CLIPROXYAPI_CONFIG_DIR=~/.cli-proxy-api + +# ── Local hostnames (Docker networking) ── +# Comma-separated additional hostnames treated as "local" for provider routing. +# Used by: open-sse/config/providerRegistry.ts — allows Docker service names. +# LOCAL_HOSTNAMES=omlx,mlx-audio + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 21. PROXY HEALTH +# ═══════════════════════════════════════════════════════════════════════════════ +# Fine-tune proxy health checking behavior. +# Used by: src/lib/proxyHealth.ts + +# Timeout for fast-fail health checks (ms). Default: 2000 +# PROXY_FAST_FAIL_TIMEOUT_MS=2000 + +# Health check result cache TTL (ms). Default: 30000 (30s) +# PROXY_HEALTH_CACHE_TTL_MS=30000 + +# Rate limit maximum wait time before failing a request (ms). Default: 120000 (2 min) +# Used by: open-sse/services/rateLimitManager.ts +# RATE_LIMIT_MAX_WAIT_MS=120000 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 22. DEBUGGING +# ═══════════════════════════════════════════════════════════════════════════════ +# These variables enable verbose debugging output. NEVER enable in production. + +# Dump Cursor protobuf decode/encode details to console. +# CURSOR_PROTOBUF_DEBUG=1 + +# Dump raw Cursor SSE stream data to console. +# CURSOR_STREAM_DEBUG=1 + +# Log Responses API SSE-to-JSON translation details. +# DEBUG_RESPONSES_SSE_TO_JSON=true + +# Enable E2E test mode — relaxes auth and enables test harness hooks. +# NEXT_PUBLIC_OMNIROUTE_E2E_MODE=true + + +# ═══════════════════════════════════════════════════════════════════════════════ +# 23. GITHUB INTEGRATION (Issue Reporting) +# ═══════════════════════════════════════════════════════════════════════════════ +# Allow users to report issues directly from the Dashboard to GitHub. +# Used by: src/app/api/v1/issues/report/route.ts + +# GitHub repository in owner/repo format. +# GITHUB_ISSUES_REPO=owner/repo + +# GitHub Personal Access Token with issues:write scope. +# GITHUB_ISSUES_TOKEN=ghp_xxxx diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a9b9a2b21..494808a234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ ## [3.6.3] — 2026-04-11 +### ✨ New Features + +- **OpenAI-Compatible Loose Validation:** Empty API keys can now be naturally submitted and saved for any `openai-compatible-*` providers (e.g. Pollinations, localized routes) directly in the UI instead of blocking save actions (#1152) +- **Cloudflare Configuration:** Updated the provider schema and UI integration for Cloudflare AI to officially expose and support the backend `accountId` field securely without overrides (#1150) + +### 🐛 Bug Fixes + +- **Vertex JSON Validation Crash:** Prevented `invalid character in header` crashes inside the `/validate` endpoint by creating a native authentication parser that correctly handles Google Identity Service Account JSON flows prior to pinging endpoints (#1153) +- **Extraneous Payload Rejection:** Globally prevented upstream `400 Bad Request` execution crashes by stripping the non-standard `prompt_cache_retention` attribute forcibly attached by Cursor/Cline IDE engines when targeting strict OpenAI/Anthropic routes (#1154) +- **Reasoning Content Drop:** Prevented pure reasoning packets, common in advanced fallback models like DeepSeek, from being aborted mid-stream by explicitly adjusting the `Empty Content (502)` circuit breakers to acknowledge `reasoning_content` states as valid (#1155) +- **Login Visual Security:** Removed the raw fallback hash dump that artificially rendered underneath the login modal in Docker instances missing `OMNIROUTE_API_KEY_BASE64` flags (#1148) + ### 🔧 Maintenance & Dependencies - **Dependabot Updates:** Safely bumped GitHub Actions `docker/build-push-action` to v7 and `actions/download-artifact` to v8 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index d59d535a0d..cd6d59b36f 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: OmniRoute API - version: 3.6.2 + version: 3.6.3 description: | OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible endpoint that routes requests to multiple AI providers with load balancing, diff --git a/electron/package.json b/electron/package.json index 7b8a6d7718..0c93989ba3 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "omniroute-desktop", - "version": "3.6.2", + "version": "3.6.3", "description": "OmniRoute Desktop Application", "main": "main.js", "author": { diff --git a/eslint.config.mjs b/eslint.config.mjs index 579a08f8b0..37fbd5f5c2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -40,6 +40,7 @@ const eslintConfig = [ "node_modules/**", // VS Code extension and its large test fixtures "vscode-extension/**", + "_mono_repo/**", // Electron app "electron/**", // Docs diff --git a/open-sse/executors/vertex.ts b/open-sse/executors/vertex.ts index cfa7b6dfe3..883446fa25 100644 --- a/open-sse/executors/vertex.ts +++ b/open-sse/executors/vertex.ts @@ -13,7 +13,7 @@ interface ServiceAccount { const TOKEN_CACHE = new Map(); -function parseSAFromApiKey(apiKey: string): ServiceAccount { +export function parseSAFromApiKey(apiKey: string): ServiceAccount { try { return JSON.parse(apiKey); } catch { @@ -21,7 +21,7 @@ function parseSAFromApiKey(apiKey: string): ServiceAccount { } } -async function getAccessToken(sa: ServiceAccount): Promise { +export async function getAccessToken(sa: ServiceAccount): Promise { if (!sa.client_email || !sa.private_key) { throw new Error( "Service Account JSON is missing required fields (client_email or private_key)" diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 3d907b03b3..188b7aac30 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -808,6 +808,7 @@ export async function handleChatCore({ delete b.disable_stream; delete b.disable_streaming; delete b.streaming; + delete b.prompt_cache_retention; } const stream = resolveStreamFlag(body?.stream, acceptHeader); diff --git a/open-sse/package.json b/open-sse/package.json index 64b3f0c920..e2a86653eb 100644 --- a/open-sse/package.json +++ b/open-sse/package.json @@ -1,6 +1,6 @@ { "name": "@omniroute/open-sse", - "version": "3.6.2", + "version": "3.6.3", "description": "Express SSE sidecar for OmniRoute — handles streaming, protocol translation, and provider orchestration", "type": "module", "main": "index.js", diff --git a/open-sse/services/errorClassifier.ts b/open-sse/services/errorClassifier.ts index fc03f1f684..12941c0ee9 100644 --- a/open-sse/services/errorClassifier.ts +++ b/open-sse/services/errorClassifier.ts @@ -17,12 +17,16 @@ export function isEmptyContentResponse(responseBody: unknown): boolean { const delta = firstChoice.delta as Record | undefined; const content = message?.content ?? delta?.content; + const reasoningContent = message?.reasoning_content ?? delta?.reasoning_content; const hasToolCalls = (Array.isArray(message?.tool_calls) && (message.tool_calls as unknown[]).length > 0) || (Array.isArray(delta?.tool_calls) && (delta.tool_calls as unknown[]).length > 0); const hasContent = content !== null && content !== undefined && content !== ""; - return !hasContent && !hasToolCalls; + const hasReasoning = + reasoningContent !== null && reasoningContent !== undefined && reasoningContent !== ""; + + return !hasContent && !hasReasoning && !hasToolCalls; } if (Array.isArray(body.content)) { diff --git a/package-lock.json b/package-lock.json index f3bbe612ad..6e2338bcff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20955,7 +20955,7 @@ }, "open-sse": { "name": "@omniroute/open-sse", - "version": "3.6.2" + "version": "3.6.3" } } } diff --git a/scratch.mjs b/scratch.mjs new file mode 100644 index 0000000000..274e16520e --- /dev/null +++ b/scratch.mjs @@ -0,0 +1,12 @@ +import { test } from "node:test"; +import assert from "node:assert"; +import { providerNodesValidateRoute } from "./src/app/api/provider-nodes/validate/route.js"; + +const req = new Request("http://localhost/api/provider-nodes/validate", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ baseUrl: "", apiKey: "" }), +}); +const res = await providerNodesValidateRoute.POST(req); +const data = await res.json(); +console.dir(data, { depth: null }); diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx index 774cfa9cb4..d1f600a35d 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx @@ -5005,6 +5005,7 @@ function AddApiKeyModal({ const defaultRegion = "us-central1"; const isGlm = provider === "glm"; const isQoder = provider === "qoder"; + const isCloudflare = provider === "cloudflare-ai"; const [formData, setFormData] = useState({ name: "", @@ -5015,6 +5016,7 @@ function AddApiKeyModal({ apiRegion: "international", validationModelId: "", customUserAgent: "", + accountId: "", }); const [validating, setValidating] = useState(false); const [validationResult, setValidationResult] = useState(null); @@ -5047,7 +5049,7 @@ function AddApiKeyModal({ }; const handleSubmit = async () => { - if (!provider || !formData.apiKey) return; + if (!provider || (!isCompatible && !formData.apiKey)) return; setSaving(true); setSaveError(null); @@ -5101,6 +5103,8 @@ function AddApiKeyModal({ providerSpecificData.region = formData.region; } else if (isGlm) { providerSpecificData.apiRegion = formData.apiRegion; + } else if (isCloudflare && formData.accountId.trim()) { + providerSpecificData.accountId = formData.accountId.trim(); } const payload = { @@ -5159,7 +5163,7 @@ function AddApiKeyModal({