mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 14:12:59 +03:00
chore(release): v3.6.3 — Fix cloudflare config, prompt cache payloads, and openai-compatible validation
This commit is contained in:
630
.env.example
630
.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
|
||||
|
||||
12
CHANGELOG.md
12
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute-desktop",
|
||||
"version": "3.6.2",
|
||||
"version": "3.6.3",
|
||||
"description": "OmniRoute Desktop Application",
|
||||
"main": "main.js",
|
||||
"author": {
|
||||
|
||||
@@ -40,6 +40,7 @@ const eslintConfig = [
|
||||
"node_modules/**",
|
||||
// VS Code extension and its large test fixtures
|
||||
"vscode-extension/**",
|
||||
"_mono_repo/**",
|
||||
// Electron app
|
||||
"electron/**",
|
||||
// Docs
|
||||
|
||||
@@ -13,7 +13,7 @@ interface ServiceAccount {
|
||||
|
||||
const TOKEN_CACHE = new Map<string, { token: string; expiresAt: number }>();
|
||||
|
||||
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<string> {
|
||||
export async function getAccessToken(sa: ServiceAccount): Promise<string> {
|
||||
if (!sa.client_email || !sa.private_key) {
|
||||
throw new Error(
|
||||
"Service Account JSON is missing required fields (client_email or private_key)"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -17,12 +17,16 @@ export function isEmptyContentResponse(responseBody: unknown): boolean {
|
||||
const delta = firstChoice.delta as Record<string, unknown> | 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)) {
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -20955,7 +20955,7 @@
|
||||
},
|
||||
"open-sse": {
|
||||
"name": "@omniroute/open-sse",
|
||||
"version": "3.6.2"
|
||||
"version": "3.6.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
scratch.mjs
Normal file
12
scratch.mjs
Normal file
@@ -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 });
|
||||
@@ -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({
|
||||
<div className="pt-6">
|
||||
<Button
|
||||
onClick={handleValidate}
|
||||
disabled={!formData.apiKey || validating || saving}
|
||||
disabled={(!isCompatible && !formData.apiKey) || validating || saving}
|
||||
variant="secondary"
|
||||
>
|
||||
{validating ? t("checking") : t("check")}
|
||||
@@ -5251,6 +5255,15 @@ function AddApiKeyModal({
|
||||
hint="ex: us-central1 ou europe-west4. Partner models usam a região global automaticamente."
|
||||
/>
|
||||
)}
|
||||
{isCloudflare && (
|
||||
<Input
|
||||
label="Account ID"
|
||||
value={formData.accountId}
|
||||
onChange={(e) => setFormData({ ...formData, accountId: e.target.value })}
|
||||
placeholder="Cloudflare Account ID"
|
||||
hint="Find it in the Cloudflare dashboard URL or settings"
|
||||
/>
|
||||
)}
|
||||
{isGlm && (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-text-main mb-1 block">API Region</label>
|
||||
@@ -5273,7 +5286,7 @@ function AddApiKeyModal({
|
||||
fullWidth
|
||||
disabled={
|
||||
!formData.name ||
|
||||
!formData.apiKey ||
|
||||
(!isCompatible && !formData.apiKey) ||
|
||||
saving ||
|
||||
(usesBaseUrl && !formData.baseUrl.trim() && !defaultBaseUrl)
|
||||
}
|
||||
@@ -5326,6 +5339,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
|
||||
validationModelId: "",
|
||||
tag: "",
|
||||
customUserAgent: "",
|
||||
accountId: "",
|
||||
});
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testResult, setTestResult] = useState(null);
|
||||
@@ -5343,6 +5357,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
|
||||
const defaultBaseUrl = getProviderBaseUrlDefault(connection?.provider);
|
||||
const isVertex = connection?.provider === "vertex";
|
||||
const isGlm = connection?.provider === "glm";
|
||||
const isCloudflare = connection?.provider === "cloudflare-ai";
|
||||
const defaultRegion = "us-central1";
|
||||
|
||||
useEffect(() => {
|
||||
@@ -5354,6 +5369,8 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
|
||||
const rawCustomUserAgent = connection.providerSpecificData?.customUserAgent;
|
||||
const existingCustomUserAgent =
|
||||
typeof rawCustomUserAgent === "string" ? rawCustomUserAgent : "";
|
||||
const rawAccountId = connection.providerSpecificData?.accountId;
|
||||
const existingAccountId = typeof rawAccountId === "string" ? rawAccountId : "";
|
||||
setFormData({
|
||||
name: connection.name || "",
|
||||
priority: connection.priority || 1,
|
||||
@@ -5365,6 +5382,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
|
||||
validationModelId: (connection.providerSpecificData?.validationModelId as string) || "",
|
||||
tag: (connection.providerSpecificData?.tag as string) || "",
|
||||
customUserAgent: existingCustomUserAgent,
|
||||
accountId: existingAccountId,
|
||||
});
|
||||
// Load existing extra keys from providerSpecificData
|
||||
const existing = connection.providerSpecificData?.extraApiKeys;
|
||||
@@ -5408,7 +5426,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
|
||||
};
|
||||
|
||||
const handleValidate = async () => {
|
||||
if (!connection?.provider || !formData.apiKey) return;
|
||||
if (!connection?.provider || (!isCompatible && !formData.apiKey)) return;
|
||||
setValidating(true);
|
||||
setValidationResult(null);
|
||||
try {
|
||||
@@ -5506,6 +5524,8 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
|
||||
updates.providerSpecificData.region = formData.region;
|
||||
} else if (isGlm) {
|
||||
updates.providerSpecificData.apiRegion = formData.apiRegion;
|
||||
} else if (isCloudflare && formData.accountId.trim()) {
|
||||
updates.providerSpecificData.accountId = formData.accountId.trim();
|
||||
}
|
||||
} else {
|
||||
// Also persist tag for OAuth accounts
|
||||
@@ -5607,7 +5627,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
|
||||
<div className="pt-6">
|
||||
<Button
|
||||
onClick={handleValidate}
|
||||
disabled={!formData.apiKey || validating || saving}
|
||||
disabled={(!isCompatible && !formData.apiKey) || validating || saving}
|
||||
variant="secondary"
|
||||
>
|
||||
{validating ? t("checking") : t("check")}
|
||||
@@ -5683,6 +5703,16 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec
|
||||
/>
|
||||
)}
|
||||
|
||||
{isCloudflare && (
|
||||
<Input
|
||||
label="Account ID"
|
||||
value={formData.accountId}
|
||||
onChange={(e) => setFormData({ ...formData, accountId: e.target.value })}
|
||||
placeholder="Cloudflare Account ID"
|
||||
hint="Find it in the Cloudflare dashboard URL or settings"
|
||||
/>
|
||||
)}
|
||||
|
||||
{isGlm && (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-text-main mb-1 block">API Region</label>
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "أعد تشغيل الخادم وسيتم استخدام كلمة المرور الجديدة",
|
||||
"backToLogin": "العودة إلى تسجيل الدخول",
|
||||
"forgotPassword": "هل نسيت كلمة المرور؟",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "إذن",
|
||||
"Content-Disposition": "التصرف في المحتوى",
|
||||
"waitingForAuthorization": "في انتظار الترخيص...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Рестартирайте сървъра - той ще използва новата парола",
|
||||
"backToLogin": "Назад към Вход",
|
||||
"forgotPassword": "Забравена парола?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Упълномощаване",
|
||||
"Content-Disposition": "Съдържание-разположение",
|
||||
"waitingForAuthorization": "Чака се оторизация...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Restartujte server - použije se nové heslo.",
|
||||
"backToLogin": "Zpět na přihlášení",
|
||||
"forgotPassword": "Zapomněli jste heslo?",
|
||||
"defaultPasswordHint": "Výchozí heslo: 123456 (pokud nebylo nastaveno INITIAL_PASSWORD)",
|
||||
"defaultPasswordHint": "Výchozí heslo: CHANGEME (pokud nebylo nastaveno INITIAL_PASSWORD)",
|
||||
"Authorization": "Autorizace",
|
||||
"Content-Disposition": "Obsah-Dispozice",
|
||||
"waitingForAuthorization": "Čekám na autorizaci...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Genstart serveren - den vil bruge den nye adgangskode",
|
||||
"backToLogin": "Tilbage til Login",
|
||||
"forgotPassword": "Glemt adgangskode?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorisation",
|
||||
"Content-Disposition": "Indhold-Disposition",
|
||||
"waitingForAuthorization": "Venter på godkendelse...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Starten Sie den Server neu – er verwendet das neue Passwort",
|
||||
"backToLogin": "Zurück zum Anmelden",
|
||||
"forgotPassword": "Passwort vergessen?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorisierung",
|
||||
"Content-Disposition": "Inhaltliche Disposition",
|
||||
"waitingForAuthorization": "Warte auf Autorisierung...",
|
||||
|
||||
@@ -2732,7 +2732,7 @@
|
||||
"restartServerWithNewPassword": "Restart the server - it will use the new password",
|
||||
"backToLogin": "Back to Login",
|
||||
"forgotPassword": "Forgot password?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Authorization",
|
||||
"Content-Disposition": "Content-Disposition",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2655,7 +2655,7 @@
|
||||
"restartServerWithNewPassword": "Reinicia el servidor; usará la nueva contraseña",
|
||||
"backToLogin": "Volver a iniciar sesión",
|
||||
"forgotPassword": "¿Olvidaste tu contraseña?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorización",
|
||||
"Content-Disposition": "Disposición de contenido",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Käynnistä palvelin uudelleen - se käyttää uutta salasanaa",
|
||||
"backToLogin": "Takaisin kirjautumiseen",
|
||||
"forgotPassword": "Unohditko salasanan?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Valtuutus",
|
||||
"Content-Disposition": "Sisältö-sijoittelu",
|
||||
"waitingForAuthorization": "Odotetaan valtuutusta...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Redémarrez le serveur - il utilisera le nouveau mot de passe",
|
||||
"backToLogin": "Retour à la connexion",
|
||||
"forgotPassword": "Mot de passe oublié ?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorisation",
|
||||
"Content-Disposition": "Disposition du contenu",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "הפעל מחדש את השרת - הוא ישתמש בסיסמה החדשה",
|
||||
"backToLogin": "חזרה לכניסה",
|
||||
"forgotPassword": "שכחת סיסמה?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "הרשאה",
|
||||
"Content-Disposition": "תוכן-נטייה",
|
||||
"waitingForAuthorization": "ממתין לאישור...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "सर्वर को पुनरारंभ करें - यह नए पासवर्ड का उपयोग करेगा",
|
||||
"backToLogin": "लॉगइन पर वापस जाएँ",
|
||||
"forgotPassword": "पासवर्ड भूल गए?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Authorization",
|
||||
"Content-Disposition": "Content-Disposition",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Indítsa újra a szervert - az új jelszót fogja használni",
|
||||
"backToLogin": "Vissza a Bejelentkezéshez",
|
||||
"forgotPassword": "Elfelejtetted a jelszavad?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Engedélyezés",
|
||||
"Content-Disposition": "Tartalom-Diszpozíció",
|
||||
"waitingForAuthorization": "Várakozás az engedélyezésre...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Mulai ulang server - server akan menggunakan kata sandi baru",
|
||||
"backToLogin": "Kembali ke Masuk",
|
||||
"forgotPassword": "Lupa kata sandi?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Otorisasi",
|
||||
"Content-Disposition": "Disposisi Konten",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Riavvia il server: utilizzerà la nuova password",
|
||||
"backToLogin": "Torna all'accesso",
|
||||
"forgotPassword": "Ha dimenticato la password?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorizzazione",
|
||||
"Content-Disposition": "Disposizione del contenuto",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "サーバーを再起動すると新しいパスワードが適用されます",
|
||||
"backToLogin": "ログインに戻る",
|
||||
"forgotPassword": "パスワードをお忘れですか?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "認可",
|
||||
"Content-Disposition": "コンテンツの配置",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "서버를 다시 시작하세요. 새 비밀번호가 사용됩니다.",
|
||||
"backToLogin": "로그인으로 돌아가기",
|
||||
"forgotPassword": "비밀번호를 잊으셨나요?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "승인",
|
||||
"Content-Disposition": "컨텐츠 처리",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Mulakan semula pelayan - ia akan menggunakan kata laluan baharu",
|
||||
"backToLogin": "Kembali ke Log Masuk",
|
||||
"forgotPassword": "Lupa kata laluan?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Keizinan",
|
||||
"Content-Disposition": "Kandungan-Pelupusan",
|
||||
"waitingForAuthorization": "Menunggu kebenaran...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Start de server opnieuw op. Deze gebruikt het nieuwe wachtwoord",
|
||||
"backToLogin": "Terug naar Inloggen",
|
||||
"forgotPassword": "Wachtwoord vergeten?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorisatie",
|
||||
"Content-Disposition": "Inhoud-dispositie",
|
||||
"waitingForAuthorization": "Wachten op toestemming...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Start serveren på nytt - den vil bruke det nye passordet",
|
||||
"backToLogin": "Tilbake til pålogging",
|
||||
"forgotPassword": "Glemt passord?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorisasjon",
|
||||
"Content-Disposition": "Innhold-Disposisjon",
|
||||
"waitingForAuthorization": "Venter på autorisasjon...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "I-restart ang server - gagamitin nito ang bagong password",
|
||||
"backToLogin": "Bumalik sa Login",
|
||||
"forgotPassword": "Nakalimutan ang password?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Awtorisasyon",
|
||||
"Content-Disposition": "Nilalaman-Disposisyon",
|
||||
"waitingForAuthorization": "Naghihintay ng awtorisasyon...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Zrestartuj serwer - będzie używał nowego hasła",
|
||||
"backToLogin": "Powrót do logowania",
|
||||
"forgotPassword": "Zapomniałeś hasła?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autoryzacja",
|
||||
"Content-Disposition": "Dyspozycja treści",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2723,7 +2723,7 @@
|
||||
"restartServerWithNewPassword": "Reinicie o servidor - ele usará a nova senha",
|
||||
"backToLogin": "Voltar para o Login",
|
||||
"forgotPassword": "Esqueceu a senha?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorização",
|
||||
"Content-Disposition": "Disposição de conteúdo",
|
||||
"waitingForAuthorization": "Aguardando autorização...",
|
||||
|
||||
@@ -2708,7 +2708,7 @@
|
||||
"restartServerWithNewPassword": "Reinicie o servidor - ele usará a nova senha",
|
||||
"backToLogin": "Voltar ao login",
|
||||
"forgotPassword": "Esqueceu a senha?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorização",
|
||||
"Content-Disposition": "Disposição de conteúdo",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Reporniți serverul - va folosi noua parolă",
|
||||
"backToLogin": "Înapoi la Logare",
|
||||
"forgotPassword": "Ai uitat parola?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorizare",
|
||||
"Content-Disposition": "Conținut-Dispoziție",
|
||||
"waitingForAuthorization": "Se așteaptă autorizația...",
|
||||
|
||||
@@ -2679,7 +2679,7 @@
|
||||
"restartServerWithNewPassword": "Перезагрузите сервер - он будет использовать новый пароль.",
|
||||
"backToLogin": "Вернуться к входу",
|
||||
"forgotPassword": "Забыли пароль?",
|
||||
"defaultPasswordHint": "Пароль по умолчанию: 123456 (если не задан INITIAL_PASSWORD)",
|
||||
"defaultPasswordHint": "Пароль по умолчанию: CHANGEME (если не задан INITIAL_PASSWORD)",
|
||||
"Authorization": "Авторизация",
|
||||
"Content-Disposition": "Содержание-Расположение",
|
||||
"waitingForAuthorization": "Ожидание авторизации...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Reštartujte server - použije nové heslo",
|
||||
"backToLogin": "Späť na Prihlásenie",
|
||||
"forgotPassword": "Zabudli ste heslo?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Autorizácia",
|
||||
"Content-Disposition": "Obsah-Dispozícia",
|
||||
"waitingForAuthorization": "Čaká sa na autorizáciu...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Starta om servern - den kommer att använda det nya lösenordet",
|
||||
"backToLogin": "Tillbaka till inloggning",
|
||||
"forgotPassword": "Glömt lösenordet?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Auktorisation",
|
||||
"Content-Disposition": "Innehåll-Disposition",
|
||||
"waitingForAuthorization": "Väntar på auktorisation...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "รีสตาร์ทเซิร์ฟเวอร์ - จะใช้รหัสผ่านใหม่",
|
||||
"backToLogin": "กลับไปที่เข้าสู่ระบบ",
|
||||
"forgotPassword": "ลืมรหัสผ่าน?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "การอนุญาต",
|
||||
"Content-Disposition": "การจัดการเนื้อหา",
|
||||
"waitingForAuthorization": "กำลังรอการอนุญาต...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Sunucuyu yeniden başlatın; yeni şifre kullanılacaktır",
|
||||
"backToLogin": "Girişe Geri Dön",
|
||||
"forgotPassword": "Şifrenizi mi unuttunuz?",
|
||||
"defaultPasswordHint": "Varsayılan şifre: 123456 (INITIAL_PASSWORD ayarlanmadığı sürece)",
|
||||
"defaultPasswordHint": "Varsayılan şifre: CHANGEME (INITIAL_PASSWORD ayarlanmadığı sürece)",
|
||||
"Authorization": "Authorization",
|
||||
"Content-Disposition": "Content-Disposition",
|
||||
"waitingForAuthorization": "Yetkilendirme bekleniyor...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Перезапустіть сервер - він використовуватиме новий пароль",
|
||||
"backToLogin": "Назад до входу",
|
||||
"forgotPassword": "Забули пароль?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Авторизація",
|
||||
"Content-Disposition": "Зміст-диспозиція",
|
||||
"waitingForAuthorization": "Очікування авторизації...",
|
||||
|
||||
@@ -2650,7 +2650,7 @@
|
||||
"restartServerWithNewPassword": "Khởi động lại máy chủ - nó sẽ sử dụng mật khẩu mới",
|
||||
"backToLogin": "Quay lại đăng nhập",
|
||||
"forgotPassword": "Quên mật khẩu?",
|
||||
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
|
||||
"defaultPasswordHint": "Default password: CHANGEME (unless INITIAL_PASSWORD was set)",
|
||||
"Authorization": "Ủy quyền",
|
||||
"Content-Disposition": "Bố trí nội dung",
|
||||
"waitingForAuthorization": "Đang chờ cấp phép...",
|
||||
|
||||
@@ -2672,7 +2672,7 @@
|
||||
"restartServerWithNewPassword": "重启服务器,系统会使用新密码",
|
||||
"backToLogin": "返回登录",
|
||||
"forgotPassword": "忘记密码?",
|
||||
"defaultPasswordHint": "默认密码:123456(除非已设置 INITIAL_PASSWORD)",
|
||||
"defaultPasswordHint": "默认密码:CHANGEME(除非已设置 INITIAL_PASSWORD)",
|
||||
"Authorization": "Authorization",
|
||||
"Content-Disposition": "Content-Disposition",
|
||||
"waitingForAuthorization": "正在等待授权...",
|
||||
|
||||
@@ -1021,6 +1021,18 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
|
||||
databricks: validateDatabricksProvider,
|
||||
snowflake: validateSnowflakeProvider,
|
||||
gigachat: validateGigachatProvider,
|
||||
vertex: async ({ apiKey }: any) => {
|
||||
try {
|
||||
const { parseSAFromApiKey, getAccessToken } =
|
||||
await import("@omniroute/open-sse/executors/vertex.ts");
|
||||
const sa = parseSAFromApiKey(apiKey);
|
||||
// Validates credentials by successfully exchanging them for a JWT from Google Identity
|
||||
await getAccessToken(sa);
|
||||
return { valid: true, error: null };
|
||||
} catch (error: any) {
|
||||
return { valid: false, error: "Invalid Service Account JSON: " + error.message };
|
||||
}
|
||||
},
|
||||
// LongCat AI — does not expose /v1/models; validate via chat completions directly (#592)
|
||||
longcat: async ({ apiKey, providerSpecificData }: any) => {
|
||||
try {
|
||||
|
||||
@@ -1023,7 +1023,7 @@ export const updateProviderNodeSchema = z.object({
|
||||
|
||||
export const providerNodeValidateSchema = z.object({
|
||||
baseUrl: z.string().trim().min(1, "Base URL and API key required"),
|
||||
apiKey: z.string().trim().min(1, "Base URL and API key required"),
|
||||
apiKey: z.string().trim().optional(),
|
||||
type: z.enum(["openai-compatible", "anthropic-compatible"]).optional(),
|
||||
compatMode: z.enum(["cc"]).optional(),
|
||||
chatPath: z.string().trim().startsWith("/").max(500).optional().or(z.literal("")),
|
||||
@@ -1106,7 +1106,7 @@ export const providersBatchTestSchema = z
|
||||
|
||||
export const validateProviderApiKeySchema = z.object({
|
||||
provider: z.string().trim().min(1, "Provider and API key required"),
|
||||
apiKey: z.string().trim().min(1, "Provider and API key required"),
|
||||
apiKey: z.string().trim().optional(),
|
||||
validationModelId: z.string().trim().optional(),
|
||||
customUserAgent: z.string().trim().max(500).optional(),
|
||||
baseUrl: z.string().trim().url().optional(),
|
||||
|
||||
@@ -756,7 +756,7 @@ test("provider-nodes validate route rejects invalid JSON and schema errors", asy
|
||||
assert.equal(invalidBodyResponse.status, 400);
|
||||
const invalidBodyPayload = await invalidBodyResponse.json();
|
||||
assert.equal(invalidBodyPayload.error.message, "Invalid request");
|
||||
assert.equal(invalidBodyPayload.error.details.length >= 2, true);
|
||||
assert.equal(invalidBodyPayload.error.details.length >= 1, true);
|
||||
});
|
||||
|
||||
test("provider-nodes validate route validates anthropic compatible providers against the models endpoint", async () => {
|
||||
|
||||
Reference in New Issue
Block a user