Compare commits

..

3 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza
91ab21a9ce chore: sync release/v3.8.51 into fix/13306-windows-libuv-abort-sqljs-exit (base-red fix #13747) 2026-09-15 23:24:33 -03:00
diegosouzapw
9fab8da48d Merge commit '8f55d85d221e8df0b788eab0e598935a1514536a' into fix/13306-windows-libuv-abort-sqljs-exit 2026-09-15 23:19:37 -03:00
diegosouzapw
ddae648fec fix(db): defer process.exit(0) by a macrotask on graceful shutdown (#13306)
sql.js's Emscripten WASM build leaves pending libuv async-handle teardown
work in flight after a statement has run. gracefulShutdown.ts's shutdown
closure called process.exit(0) in the same tick as the cleanup promise
resolving, tearing the event loop down before that teardown settled. On
Windows, libuv's async-handle close path asserts
!(handle->flags & UV_HANDLE_CLOSING) when this happens, aborting the
process (exit 127); Linux's unix backend has no equivalent assertion,
which is why this was invisible on CI. Deferring process.exit(0) by one
macrotask (setTimeout(..., 0)) mirrors the pattern already used
throughout 9router's own shutdown call sites and gives sql.js's pending
libuv work a chance to settle first.

Regression test: tests/unit/graceful-shutdown-deferred-exit-13306.test.ts
pins the ordering contract (process.exit(0) must not fire in the same
microtask turn cleanup() resolves in). The Windows abort itself cannot
be reproduced on Linux CI — see the PR body for the required live
Windows validation.
2026-09-15 15:17:33 -03:00
11683 changed files with 312014 additions and 3171983 deletions

View File

@@ -112,9 +112,8 @@ DISABLE_SQLITE_AUTO_BACKUP=false
# Example: redis://localhost:6379 (or redis://redis:6379 in Docker)
# REDIS_URL=redis://localhost:6379
# Namespace prefix for ALL OmniRoute Redis keys (rate limiter + auth cache +
# quota store + warmup circuit breaker). Prevents key collisions when OmniRoute
# shares a Redis instance with other apps (e.g. on 127.0.0.1:6379). Default when
# unset: omniroute:
# quota store). Prevents key collisions when OmniRoute shares a Redis instance
# with other apps (e.g. on 127.0.0.1:6379). Default when unset: omniroute:
# REDIS_KEY_PREFIX=omniroute:
# Host interface docker-compose publishes the Redis sidecar on.
# Default: 127.0.0.1 (loopback only). The compose Redis runs WITHOUT
@@ -173,11 +172,6 @@ PORT=20128
# stay consistent without relying on window.location.origin alone:
# NEXT_PUBLIC_BASE_URL=https://host/omniroute
#
# Client-side fallback port for display URLs when no origin is known (SSR/tests):
# read before PORT so a browser bundle built with a different public port still
# renders the right http://localhost:<port> links (src/shared/hooks/useDisplayBaseUrl.ts).
# NEXT_PUBLIC_PORT=20128
#
# Explicit path probed by the container health check. Unset, the probe derives it
# from OMNIROUTE_BASE_PATH; setting it opts back into the deep monitoring endpoint.
# Used by: scripts/dev/healthcheck.mjs
@@ -293,9 +287,9 @@ OMNIROUTE_USE_TURBOPACK=1
# OMNIROUTE_SKIP_DB_HEALTHCHECK=1
# Interval (ms) for the background credential health check scheduler.
# Default: 3600000 (60 minutes). Minimum: 10000 (10 seconds).
# Default: 300000 (5 minutes). Minimum: 10000 (10 seconds).
# Used by: open-sse/config/constants.ts, src/lib/credentialHealth/scheduler.ts
# CREDENTIAL_HEALTH_CHECK_INTERVAL=3600000
# CREDENTIAL_HEALTH_CHECK_INTERVAL=300000
# TTL (ms) for cached credential health status.
# Default: 300000 (5 minutes).
@@ -546,48 +540,6 @@ ALLOW_API_KEY_REVEAL=false
# When unset, OmniRoute uses the per-feature defaults. Set to "false"/"0" to disable.
# OUTBOUND_SSRF_GUARD_ENABLED=true
# ── Self-hosted unified OpenAI-compatible entry (RIC-738, D4) ────────────────────
# When set, /v1/chat/completions diverts to the self-hosted provider adapters
# (open-sse/services/selfHostedEntry.ts) instead of the cloud pipeline. YAML inline
# (example) — or point OMNIROUTE_SELF_HOSTED_PROVIDERS_FILE at a YAML file. Secrets
# are runtime-only, never logged. While ANY of these is set, the entry is active;
# config present but unparseable returns a 500 (never silently falls through).
# OMNIROUTE_SELF_HOSTED_PROVIDERS='
# providers:
# - id: local
# kind: openai
# baseUrl: http://127.0.0.1:11434/v1
# model: llama3
# - id: claude
# kind: anthropic
# baseUrl: http://127.0.0.1:8080
# model: claude-sonnet
# '
# OMNIROUTE_SELF_HOSTED_PROVIDERS_FILE=/etc/omniroute/providers.yaml
# Optional shared API key for the unified entry (D5 reserved). When set, require
# `Authorization: Bearer <key>`; empty = open loopback/trusted-network route.
# OMNIROUTE_SELF_HOSTED_API_KEY=
# ── Deterministic routing strategies (M2 / RIC-740, D3 可审计路由) ─────────────
# Optional `strategy:` block — either inline in the providers document above, or a
# standalone document via these env vars. One rule per line; every decision is
# explainable via the `x-omniroute-route-decision` response header. No ML/predict.
# Malformed strategy config returns a 500 (never silently becomes a no-op).
# Example (inline, same shape as `strategy:` inside the providers YAML):
# OMNIROUTE_SELF_HOSTED_STRATEGY='
# blacklist: []
# whitelist: [cheap, fast, premium]
# costPriority: true
# latencyAware:
# enabled: true
# cooldown:
# consecutiveFailures: 2
# cooldownMs: 30000
# fallbackChain: [cheap, fast, premium]
# '
# OMNIROUTE_SELF_HOSTED_STRATEGY_FILE=/etc/omniroute/strategy.yaml
# See docs/routing/DETERMINISTIC_ROUTING.md for the full strategy surface.
# ═══════════════════════════════════════════════════════════════════════════════
# 5. INPUT SANITIZATION & PII PROTECTION (FASE-01)
# ═══════════════════════════════════════════════════════════════════════════════
@@ -669,15 +621,6 @@ ALLOW_API_KEY_REVEAL=false
# Validated to >= 1, clamped to <= 32. | Default: 3
# COMBO_CONCURRENCY_PER_MODEL=3
# Disable conversation-history tracking (#13150).
# Used by: open-sse/services/conversationTracker.ts. resolveConversationId()
# returns an untracked result before it reads SQLite or parses message history,
# and the switch also covers client-supplied session IDs. Routing sessions are
# unaffected and existing records are not deleted. Use it when the dashboard's
# conversation view is unused and the turn table has grown large.
# Set to 1 to disable. | Default: unset (tracking enabled)
# OMNIROUTE_DISABLE_CONVERSATION_TRACKING=1
# ═══════════════════════════════════════════════════════════════════════════════
# 7. URLS & CLOUD SYNC
# ═══════════════════════════════════════════════════════════════════════════════
@@ -768,26 +711,14 @@ NEXT_PUBLIC_CLOUD_URL=
# OpenCode Go/Zen VPS egress (#5997): on a datacenter VPS, Cloudflare in front of
# opencode.ai/zen/go 403s chat requests that lack OpenCode CLI identity headers.
# When your clients don't already send them, set this to synthesize the CLI headers
# (User-Agent, x-opencode-client, x-opencode-project, canonical request/session ids) on
# absent keys. ON by default — a client value always wins, these only fill gaps.
# (User-Agent, x-opencode-client, x-opencode-project, fresh request/session UUIDs) on
# absent keys. OFF by default — forward-only is safer when clients already send them.
# Values are overridable via OPENCODE_GO_USER_AGENT / OPENCODE_USER_AGENT / OPENCODE_CLIENT /
# OPENCODE_PROJECT (defaults: opencode/1.18.31 / desktop / global).
# OPENCODE_PROJECT (defaults: opencode-cli/1.0.0 / cli / default).
#OPENCODE_SYNTHESIZE_CLI_HEADERS=true
#OPENCODE_USER_AGENT=opencode/1.18.31
#OPENCODE_CLIENT=desktop
#OPENCODE_PROJECT=global
# Keyless OpenCode models are answered only when the request declares a non-empty tool
# list, and the upstream inspects which names it carries. OmniRoute reuses the list a
# request of the same conversation was last seen getting through, so a request that
# carries none — a title or a summary — goes out with the list its own client already
# declared. Set to off to stop adjusting request bodies entirely; headers are unaffected.
#OPENCODE_FREE_TIER_REQUEST_CONTRACT=off
# Tool names to declare when nothing has been observed yet for a model, comma-separated.
# Empty falls back to a single placeholder the model is told not to call. Only useful on
# an install where no client sends tools, since there is then nothing to learn from.
#OPENCODE_FREE_TIER_PLACEHOLDER_TOOLS=glob,grep,read
#OPENCODE_USER_AGENT=opencode-cli/1.0.0
#OPENCODE_CLIENT=cli
#OPENCODE_PROJECT=default
# Ollama Cloud quota scraping. Prefer configuring this per connection in
# Dashboard → Providers → Ollama Cloud. The cookie is sensitive.
@@ -1109,12 +1040,6 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# Used by: src/lib/jobs/reasoningCacheCleanupJob.ts.
#OMNIROUTE_REASONING_CACHE_CLEANUP_INTERVAL_MS=1800000
# Opt-in minimum output budget (tokens) for reasoning models (#10281 follow-up).
# When set, a caller max_tokens in [256, floor) on a thinking-capable model is
# raised to the floor so reasoning tokens cannot consume the whole budget
# (zero-content finish_reason=length turns). Unset = never enlarge client budgets (#9507).
#OMNIROUTE_REASONING_MIN_BUDGET=4096
# Spend write batcher cadence (ms) and buffer size before forced flush.
# Used by: src/lib/spend/batchWriter.ts. Defaults: 60000 ms / 1000 entries.
#OMNIROUTE_SPEND_FLUSH_INTERVAL_MS=60000
@@ -1161,11 +1086,6 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# Used by: open-sse/services/compression/engines/rtk/filterLoader.ts. Default: 0.
#OMNIROUTE_RTK_TRUST_PROJECT_FILTERS=0
# Character cap for Lite proactive tool-result truncation when lite.maxToolLength
# is unset. Range 256-1000000. Dashboard setting wins over this env.
# Used by: open-sse/services/compression/lite.ts. Default: 2000.
#OMNIROUTE_LITE_MAX_TOOL_LENGTH=2000
# Maximum concurrent synchronous compression workers. Excess jobs wait FIFO.
# Used by: open-sse/services/compression/compressionWorkerPool.ts. Default: 2.
#OMNI_COMPRESSION_WORKERS=2
@@ -1222,37 +1142,25 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# Used by: src/lib/db/core.ts::getDbHealthCheckIntervalMs().
#OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS=21600000
# Removed: periodic live wal_checkpoint(TRUNCATE) could SIGBUS the process (issue
# #13973). The variable is inert: a positive value logs a one-time deprecation warning,
# while 0 or unset stays silent. The WAL is kept small
# by the PASSIVE scheduler below and truncated by the shutdown checkpoint.
# WAL truncate cadence override (ms). Set to 0 to disable. Default: 21600000 (6h).
# Used by: src/lib/db/core.ts::getWalTruncateIntervalMs().
#OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS=21600000
# Frequent wal_checkpoint(PASSIVE) cadence (ms). Set to 0 to disable. Default: 300000 (5m).
# Used by: src/lib/db/walMaintenance.ts.
#OMNIROUTE_WAL_PASSIVE_INTERVAL_MS=300000
# WAL size (MB) above which a PASSIVE tick runs wal_checkpoint(RESTART) so the
# WAL starts over without rewriting the mapped wal-index. Default: 256.
# WAL size (MB) above which a PASSIVE tick escalates to wal_checkpoint(TRUNCATE). Default: 256.
# Used by: src/lib/db/walMaintenance.ts.
#OMNIROUTE_WAL_GUARD_MAX_MB=256
# Minimum rows a cleanup must delete before the post-cleanup VACUUM runs. Default: 1000.
# 0 always vacuums when rows were freed. Used by: src/lib/db/cleanup.ts.
#OMNIROUTE_VACUUM_MIN_DELETED_ROWS=1000
# Explicit path to sql-wasm.wasm for the sql.js fallback adapter. Default: auto-detect.
# Used by: src/lib/db/adapters/sqljsAdapter.ts.
#OMNIROUTE_SQLJS_WASM_PATH=
# Days a terminal (completed/failed/cancelled/expired) Batch API job's checkpoints,
# referenced files, and row are kept by the automatic cleanup sweep. Default: 30
# (matches OpenAI's own Batch API output retention window). Only takes effect once
# BATCH_AND_FILE_AUTO_CLEANUP_ENABLED is turned on.
# Used by: src/lib/db/cleanup.ts::getBatchRetentionDays().
#OMNIROUTE_BATCH_RETENTION_DAYS=30
# Let the automatic cleanup sweep delete terminal Batch API jobs (and their
# checkpoints) past OMNIROUTE_BATCH_RETENTION_DAYS, and clear the content of
# uploaded files past their own expires_at. Off by default: every existing
# install keeps this data exactly as before until an operator opts in.
# Used by: src/lib/db/cleanup.ts (feature flag; see docs/reference/FEATURE_FLAGS.md).
#BATCH_AND_FILE_AUTO_CLEANUP_ENABLED=false
# Skip the Redis-backed auth cache used by API key lookups (forces DB reads).
# Used by: src/lib/db/apiKeys.ts. Set to 1 to disable. Default: enabled.
@@ -1409,10 +1317,6 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
# VISION_BRIDGE_BASE_URL=
# VISION_BRIDGE_API_KEY=
# How long a "no usable vision candidate" outcome is remembered, in ms.
# Invalid or negative values fall back to the default; 0 disables the negative cache.
# OMNIROUTE_VISION_BRIDGE_NEGATIVE_CACHE_MS=30000
# ─────────────────────────────────────────────────────────────────────────────
# ⚠️ GOOGLE OAUTH (Antigravity) & OTHER PROVIDERS — REMOTE SERVERS
# ─────────────────────────────────────────────────────────────────────────────
@@ -1452,7 +1356,7 @@ CLAUDE_USER_AGENT="claude-cli/2.1.258 (external, cli)"
# forward the original names verbatim (debugging only).
# CLAUDE_DISABLE_TOOL_NAME_CLOAK=false
# Optional override; leave unset to follow the shared Codex client version.
# CODEX_USER_AGENT="codex-cli/0.155.0 (Windows 10.0.26200; x64)"
# CODEX_USER_AGENT="codex-cli/0.153.4 (Windows 10.0.26200; x64)"
GITHUB_USER_AGENT="GitHubCopilotChat/0.54.0"
ANTIGRAVITY_USER_AGENT="antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0"
KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"
@@ -1472,7 +1376,7 @@ CURSOR_USER_AGENT="Cursor/3.4"
# Override Codex client version sent in headers independently of the
# CODEX_USER_AGENT string. Used by: open-sse/config/codexClient.ts.
# CODEX_CLIENT_VERSION=0.155.0
# CODEX_CLIENT_VERSION=0.153.4
#
# Override the advertised Claude Code client version independently of
# CLAUDE_USER_AGENT. Anthropic gates some models (Fable 5.1) on this
@@ -1483,13 +1387,6 @@ CURSOR_USER_AGENT="Cursor/3.4"
# Override the advertised GitHub Copilot CLI version independently of
# GITHUB_USER_AGENT. Used by: open-sse/config/providerHeaderProfiles.ts.
# GITHUB_COPILOT_CLI_VERSION=1.0.82
#
# Pin the `copilot-integration-id` header sent to standard GitHub Copilot,
# overriding the default copilot-developer-cli identity (and disabling the
# automatic 403-identity fallback to copilot-chat). Set this only if your
# Copilot account/org requires a specific integration id. Used by:
# open-sse/config/providerHeaderProfiles.ts, open-sse/executors/copilotIdentityFallback.ts.
# COPILOT_INTEGRATION_ID=copilot-chat
# Kill-switch to strip non-standard `codex.*` SSE events (e.g. codex.rate_limits)
# from the Codex Responses stream. These frames break the OpenAI SDK's
@@ -1597,7 +1494,7 @@ CURSOR_USER_AGENT="Cursor/3.4"
#
# Hierarchy: REQUEST_TIMEOUT_MS acts as a global override.
# If set, it becomes the default for FETCH_TIMEOUT_MS, STREAM_IDLE_TIMEOUT_MS,
# and STREAM_READINESS_TIMEOUT_MS. STREAM_ACTIVE_TIMEOUT_MS is independent.
# and STREAM_READINESS_TIMEOUT_MS.
# The fine-grained variables below override their respective defaults only when set.
# ── Global shortcut ──
@@ -1618,18 +1515,6 @@ CURSOR_USER_AGENT="Cursor/3.4"
# # caller's deadline; on expiry the request retries
# # once on a fresh no-keep-alive socket. 0 disables
# # the bound (default: 30000 = 30s).
# OMNIROUTE_DIRECT_RESPONSE_RETRY_TIMEOUT_MS=600000 # Ceiling (ms) for the fresh-socket
# # RETRY attempt above (#13703). Only applies when
# # the caller already attached its own deadline
# # signal (the resolved connection/model/provider/
# # FETCH_TIMEOUT_MS cascade) — that signal is the
# # real bound and fires first in the intended path,
# # so this is a generous backstop rather than a flat
# # cap: without it the retry reused the same short
# # OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS window as the
# # pooled attempt and 504'd healthy slow-TTFB
# # reasoning models. Never allowed below the flat
# # floor above (default: 600000 = 10 min).
# Default timeout (ms) for src/shared/utils/fetchTimeout.ts. Acts as the
# fallback when FETCH_TIMEOUT_MS is unset. Default: 120000 (2 min).
@@ -1791,8 +1676,6 @@ CURSOR_USER_AGENT="Cursor/3.4"
# ── Stream idle detection ──
# STREAM_IDLE_TIMEOUT_MS=600000 # Max silence between SSE chunks (default: 600000)
# # Extended-thinking models rarely pause >90s.
# STREAM_ACTIVE_TIMEOUT_MS=1260000 # Max total active SSE lifetime (default: 21 min = the largest registered model timeoutMs + 1 min; 0 disables)
# # Independent of REQUEST_TIMEOUT_MS and byte activity.
# STREAM_READINESS_TIMEOUT_MS=80000 # Time to receive the first non-ping SSE event
# STREAM_READINESS_MAX_TIMEOUT_MS=180000 # Cap for adaptive first-event extensions
# # (large/tool-heavy/high-reasoning requests).
@@ -1810,8 +1693,6 @@ CURSOR_USER_AGENT="Cursor/3.4"
# TLS_CLIENT_TIMEOUT_MS=600000 # Inherits from FETCH_TIMEOUT_MS by default
# TLS_FIRST_BYTE_WATCHDOG_MS=10000 # #12656: bounds time-to-first-byte on the wreq body (0 disables)
# OPENCODE_RESPONSES_STALL_ROTATION=false # #13484 feature flag (Settings → Feature Flags wins): rotate once when a streamed Responses reply stalls before its first byte
# OPENCODE_PARK_AND_RESUME=false # #13924 feature flag (Settings → Feature Flags wins): park the request with a heartbeat after repeated transient 429s, then replay one capped leg of up to 3 accounts
#OPENCODE_POOL_STRAIN_MARKER_PATH=/tmp/opencode-pool-strain.json # #13924: pool-strain marker path (JSON {since, reason, ttl_s}); fresh marker parks without recounting
# RESPONSES_FIRST_BYTE_TIMEOUT_MS=15000 # #13484: OpenCode Responses first-byte window, only used when the OPENCODE_RESPONSES_STALL_ROTATION flag is on (0 disables)
# ── API Bridge (/v1 proxy server) ──
@@ -1901,8 +1782,8 @@ APP_LOG_TO_FILE=true
# bodies is retained in the database.
# Used by: open-sse/handlers/chatCore.ts — cloneBoundedChatLogPayload()
# CHAT_LOG_TEXT_LIMIT=65536 # Max string length before truncation (default: 64 KB)
# CHAT_LOG_ARRAY_TAIL_ITEMS=1000 # Number of array items retained from tail (default: 1000)
# CHAT_LOG_MAX_DEPTH=20 # Max nesting depth before truncation (default: 20)
# CHAT_LOG_ARRAY_TAIL_ITEMS=128 # Number of array items retained from tail (default: 128)
# CHAT_LOG_MAX_DEPTH=6 # Max nesting depth before truncation (default: 6)
# CHAT_LOG_MAX_OBJECT_KEYS=80 # Max object keys retained (default: 80, 0 = no limit)
# CHAT_LOG_MAX_BODY_KB=1024 # Whole request/response body size before it's replaced by a bare
# {_truncated, messageCount, ...} summary instead of the full clone
@@ -2276,13 +2157,6 @@ APP_LOG_TO_FILE=true
# Used by: src/lib/services/bootstrap.ts, src/app/api/services/mux/_lib.ts
# MUX_SERVICE_PORT=8322
# ── open-wa embedded service ──
# Override the port where the embedded open-wa (WhatsApp Web automation)
# daemon listens. Always bound to 127.0.0.1 — never configurable to 0.0.0.0.
# Rarely needed — defaults to 8323.
# Used by: src/lib/services/bootstrap.ts
# OPENWA_SERVICE_PORT=8323
# ── Dario embedded service ──
# Override the host/port the embedded Dario (Claude Code subscription proxy)
# daemon binds to and is reached at. Always bound to 127.0.0.1 — never
@@ -2336,11 +2210,6 @@ APP_LOG_TO_FILE=true
# PROXY_HEALTH_ENABLED=true
# Sweep interval in ms (minimum 60000). Default: 600000 (10min).
# PROXY_HEALTH_INTERVAL_MS=600000
# Background recovery-pass interval in ms: how often the scheduler re-probes proxies it
# previously marked unhealthy, so a proxy that comes back is picked up without a restart.
# Values below 60000 fall back to the default.
# PROXY_HEALTH_RECOVERY_INTERVAL_MS=600000
# Reachability probe target for the scheduler and the auto-test endpoint.
# Point it at an internal/self-hosted URL to avoid the public default.
# PROXY_HEALTH_TEST_URL=https://httpbin.org/ip
@@ -2488,10 +2357,6 @@ APP_LOG_TO_FILE=true
# Cursor stream idle timeout (ms). Default: 300000 (5 min).
# Used by: open-sse/executors/cursor.ts.
# CURSOR_STREAM_TIMEOUT_MS=300000
# Grace window (ms) after a composer kv_after_text soft terminator when bytes remain
# buffered — gives a trailing exec_mcp tool call time to complete its frame. 2s covers
# every exec_mcp-behind-kv ordering observed live.
# CURSOR_KV_GRACE_MS=2000
# Cursor tool-commit directive toggle. Default-on: when a request declares
# tools, a directive is prepended so composer-2.5 reliably issues tool calls
@@ -2499,22 +2364,6 @@ APP_LOG_TO_FILE=true
# Used by: open-sse/executors/cursor.ts.
# CURSOR_TOOL_DIRECTIVE=1
# Operator-defined system prompt text appended to the system message AFTER
# translation (post-translation injection), so it reaches codex/Responses and
# /v1/messages paths. Also used as the directive prefix stripped from echoed
# system preamble blocks. Leave unset to disable.
# Used by: open-sse/translator/request/claude-to-openai.ts, open-sse/translator/response/openai-to-claude.ts.
# OMNIROUTE_SYSTEM_INSTRUCTION_APPEND=
# Set to "1" to also strip echoed system-prompt PREAMBLE blocks
# (<analysis>/<system-reminder>/<summary> blocks, prose reproductions of the skill
# section) from the start of an openai->claude stream. OFF by default: it recognises
# constructs by English-prose heuristics and DOES mutate the response payload, so a
# reply that genuinely opens with such a section would lose it. Turn it on only when
# you actually hit the system-echo leak.
# Used by: open-sse/translator/response/openai-to-claude.ts, open-sse/utils/directivePreambleStripper.ts.
# OMNIROUTE_STRIP_SYSTEM_PREAMBLE=0
# Per-image fetch timeout (ms) for remote image_url vision input. Default: 15000.
# Used by: open-sse/utils/cursorImages.ts.
# CURSOR_IMAGE_FETCH_TIMEOUT_MS=15000
@@ -2711,16 +2560,6 @@ APP_LOG_TO_FILE=true
# for root-less / user-namespaced deployments (e.g. rootless Docker/Podman)
# where the operator trusts the CA manually (e.g. via Node's extra-CA-certs mechanism).
# OMNIROUTE_NO_SUDO=0
# ── Antigravity MITM bridge (bin/antigravity-bridge.mjs) ──
# Local HTTPS listener that fronts the Antigravity IDE and forwards to the router.
# BRIDGE_PORT: port the bridge listens on. Defaults to 20129.
# ROUTER_URL: where it forwards /v1/antigravity traffic. Defaults to the local router.
# CERT_DIR: directory holding server.key/server.crt for the bridge's TLS listener.
# Defaults to ~/.omniroute/mitm (the MITM CA directory).
# BRIDGE_PORT=20129
# ROUTER_URL=http://127.0.0.1:20128/v1/antigravity
# CERT_DIR=~/.omniroute/mitm
# Explicit opt-out: skip provisioning /etc/hosts DNS entries for the Antigravity
# proxy hostnames entirely (containers with no sudo/root available).
# Used by: src/mitm/dns/provision.ts.
@@ -2757,16 +2596,6 @@ APP_LOG_TO_FILE=true
# When enabled, the node authenticates with the API key stored on its connection.
# AUDIO_REMOTE_PROVIDER_NODES=false
# Used by: src/app/api/v1/_shared/rerankProviderNodes.ts — lets POST /v1/rerank (and
# the memory engine's loopback rerank step) use an OpenAI-compatible provider node
# hosted outside localhost, e.g. a LAN box or Tailscale peer running TEI/Infinity/vLLM.
# OFF by default: routing to a remote host changes egress identity, so it must be an
# explicit operator decision. Loopback/private nodes (localhost, 127.0.0.1,
# 172.16-31.x) are always allowed and unaffected by this flag. Remote nodes must also
# pass the provider outbound URL policy (see OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS);
# cloud-metadata hosts are never routed to.
# RERANK_REMOTE_PROVIDER_NODES=false
# ── Free Proxy Pool (auto-sync scheduler) ──
# Background refresh of the free-proxy pool. Opt-in, OFF by default (parallels
# Hard Rule #20's default-off posture for data-mutating background features).
@@ -2919,13 +2748,6 @@ APP_LOG_TO_FILE=true
# tokens (accessToken / refreshToken / providerSpecificData). Default OFF —
# only non-credential metadata is synced. See docs/security/SOCKET_DEV_FINDINGS.md §5.
# OMNIROUTE_CLOUD_SYNC_SECRETS=false
#
# Set to "true" to reject an UNSIGNED Cloud sync response when no local secret
# is configured (#13679). Default OFF keeps v3.8.x back-compat for peers that
# have not rotated in a shared secret yet; v3.9 flips the default to enforced.
# A signature that IS present is always verified, and always rejected when
# OMNIROUTE_CLOUD_SYNC_SECRET is unset, regardless of this flag.
# OMNIROUTE_CLOUD_SYNC_ENFORCE_SIGNATURE=false
# ─── Zed import legacy compat (v3.8.6) ──────────────────────────────────────
# Set to "true" to fall back to the v3.8.5 one-step "import everything from
@@ -3221,13 +3043,6 @@ QUOTA_STORE_DRIVER=sqlite
# CHATGPT_WEB_CODEX_CHROME_PATH=/usr/bin/chromium
# CHROME_PATH=/usr/bin/chromium
# CHATGPT_WEB_CODEX_CDP_URL=http://chatgpt-web-codex-browser:9223
# CDP_PROXY_TOKEN required by docker/chatgpt-web-codex-browser/cdp-proxy.mjs (#13679):
# when set, every request to the CDP proxy sidecar must present it as an
# `X-Omni-Cdp-Token` header. Left unset, the proxy keeps forwarding requests
# unauthenticated (network isolation via docker-compose.yml's dedicated
# `chatgpt-web-codex-net` is the default mitigation). Generate with:
# `openssl rand -hex 32`
# CDP_PROXY_TOKEN=
# CHATGPT_WEB_CODEX_TUNNEL_ID=tunnel_0123456789abcdef0123456789abcdef
# CHATGPT_WEB_CODEX_RUNTIME_KEY=
# CHATGPT_WEB_CODEX_CONNECTOR_NAME=OmniRoute Codex v2

View File

@@ -1,41 +0,0 @@
# ──────────────────────────────────────────────────────────────────────
# OmniRoute — Self-Host env (minimal, zero-fee self-host)
# ──────────────────────────────────────────────────────────────────────
# cp .env.selfhost.example .env
# Edit only the two lines marked `# EDIT ME`. Everything else has a sane
# default. No secrets are baked in — OmniRoute never ships credentials.
#
# Full variable reference: docs/guides/DOCKER_GUIDE.md and .env.example
# ──────────────────────────────────────────────────────────────────────
# ── Ports (host-side) ──────────────────────────────────────────────────
# Dashboard + API + Live-WS. Already match the image defaults.
DASHBOARD_PORT=20128
API_PORT=20129
LIVE_WS_PORT=20132
# ── Bind address ───────────────────────────────────────────────────────
# 127.0.0.1 = loopback only (safe with REQUIRE_API_KEY=false, the default).
# Set to 0.0.0.0 ONLY when REQUIRE_API_KEY=true OR a reverse proxy
# enforces auth upstream. Exposing an unauthenticated /v1 proxy on the
# LAN/WAN lets anyone burn your provider quotas. # EDIT ME if you must.
APP_BIND_HOST=127.0.0.1
# ── Auth ──────────────────────────────────────────────────────────────
# false = the dashboard and /v1 proxy are open to APP_BIND_HOST's network.
# true = every request needs an API key / dashboard login. The dashboard
# auto-creates INITIAL_PASSWORD on first boot (read it from the logs:
# `docker logs omniroute | grep -i password`). # EDIT ME — set true.
REQUIRE_API_KEY=false
# INITIAL_PASSWORD= # uncomment to pre-seed the dashboard password
# ── Memory ceiling (V8 old-space) ──────────────────────────────────────
# 1024 = dashboard + light chat. Coding agents (long POST /v1/responses
# bodies) need more — see SELF_HOST_GUIDE.md "sizing". 2048 is a safe
# default for a single user who runs Claude Code / Codex through it.
OMNIROUTE_MEMORY_MB=2048
# ── Browser-facing origin (optional) ───────────────────────────────────
# Set ONLY if you expose OmniRoute behind a domain via a reverse proxy.
# NEXT_PUBLIC_BASE_URL=https://your-domain.example.com
# BASE_URL=http://omniroute:20128

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Report a bug or unexpected behavior in OmniRoute
title: "fix(): "
title: "[BUG] "
labels: ["bug"]
body:
- type: markdown
@@ -8,8 +8,6 @@ body:
value: |
Thanks for taking the time to report a bug. Please fill out the sections below so we can reproduce and fix the issue.
The title is prefilled as `fix(): ` to match the [Conventional Commits](https://github.com/diegosouzapw/OmniRoute/blob/main/CONTRIBUTING.md#commit-messages) convention — pick a scope from the list documented there (e.g. `providers`, `resilience`, `dashboard`, `api`).
- type: input
id: version
attributes:

View File

@@ -1,6 +1,6 @@
name: Feature Request
description: Suggest a new feature or improvement for OmniRoute
title: "feat(): "
title: "[Feature] "
labels: ["enhancement"]
body:
- type: markdown
@@ -8,8 +8,6 @@ body:
value: |
Thanks for suggesting a feature! Please describe the problem you're trying to solve and how you'd like it to work.
The title is prefilled as `feat(): ` to match the [Conventional Commits](https://github.com/diegosouzapw/OmniRoute/blob/main/CONTRIBUTING.md#commit-messages) convention — pick a scope from the list documented there (e.g. `providers`, `resilience`, `dashboard`, `api`).
- type: textarea
id: problem
attributes:

View File

@@ -144,16 +144,6 @@ jobs:
- run: npm run check:test-discovery
- run: npm run check:radar-sentinels
- run: npm run check:tracked-artifacts
- name: AI attribution in commit / PR metadata (Hard Rule #16)
if: github.event_name == 'pull_request'
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
printf '%s' "$PR_BODY" > "$RUNNER_TEMP/pr-body.md"
npm run check:ai-attribution -- --range "$PR_BASE_SHA..$PR_HEAD_SHA" --pr-title "$PR_TITLE" --pr-body-file "$RUNNER_TEMP/pr-body.md"
# A test parked in vitest.config.ts's exclude list does not run, and looks like
# coverage to whoever reads the tree. 62 files accumulated behind a comment pointing
# at #8618 — closed in August while the list grew to 62; 51 of them passed when
@@ -463,11 +453,8 @@ jobs:
# One FS inventory of src/app/api for both anti-hallucination directions.
- name: API docs refs (openapi + prose → routes)
run: npm run check:api-docs-refs
# Blocking since the 2026-09 docs re-sync: a core doc edited without `npm run i18n:run
# --files=<doc>` (or `--adopt` for a mechanical edit) leaves 65 stale mirrors behind;
# the run only retranslates the `## ` sections whose text changed, so it is cheap.
- name: i18n docs drift (sources changed since their translation)
run: node scripts/i18n/check-translation-drift.mjs
- name: i18n translation drift (warn)
run: node scripts/i18n/check-translation-drift.mjs --warn
docs-lint:
name: Docs Lint (prose — advisory)
@@ -546,20 +533,6 @@ jobs:
env:
BASE_REF: ${{ github.base_ref && format('origin/{0}', github.base_ref) || '' }}
run: node scripts/i18n/check-new-key-coverage.mjs
# Absolute complement of the two gates above: every locale must carry exactly the key
# set of en.json, whatever the age of the key. A locale batch is generated from the
# en.json of the day the branch is cut and translates for days while the base keeps
# adding keys — the batch PR adds no key itself, so the new-key gate stays silent and
# 43 absent keys out of ~13,000 still read 99.7 % coverage. Incident 2026-09-15:
# batch 1 (#13044) landed 43 keys short in nine locales, batch 2 (#13660) 10 keys short
# in eight. Fix is `sync-ui-keys --locale=<codes> --translate-markers`.
- name: i18n key completeness (every locale carries every en.json key)
run: node scripts/i18n/check-key-completeness.mjs
# Same gate for the CLI catalogs (bin/cli/locales). check:cli-i18n only compares
# pt-BR / zh-CN / zh-TW; 38 locales shipped with 124 of 830 keys for months
# (audit 2026-09-16) and the CLI silently fell back to English for them.
- name: i18n key completeness (CLI catalogs)
run: node scripts/i18n/check-key-completeness.mjs --catalog=cli
# #8038: cheap glossary/protected-terms consistency gate —
# complements i18n-ui-coverage (key parity) and the ICU `i18n` job below
@@ -1123,7 +1096,7 @@ jobs:
# stalled upload can neither eat the job's budget nor turn a green job cancelled.
timeout-minutes: 5
continue-on-error: true
uses: codecov/codecov-action@0b35c9ecc4f0529d0eb674914510c22f85b196b4 # v7.1.0
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: coverage/lcov.info
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -22,10 +22,10 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
- uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
languages: javascript-typescript
queries: security-extended
- uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
- uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
category: "/language:javascript-typescript"

View File

@@ -411,12 +411,16 @@ jobs:
path: /tmp/digests/bun-web
merge-multiple: true
- name: Create Docker Hub version manifests
- name: Create Docker Hub manifest
run: |
set -euo pipefail
create_manifest() {
local image="$1" suffix="$2" dir="$3" optional="${4:-}"
local tags=(-t "${image}:${VERSION}${suffix}")
if [ "$PROMOTE_LATEST" = "true" ]; then
tags+=(-t "${image}:latest${suffix}")
fi
local refs=()
while IFS= read -r digest_file; do
refs+=("${image}@sha256:$(basename "$digest_file")")
@@ -429,7 +433,7 @@ jobs:
echo "No image digests in $dir" >&2
exit 1
fi
docker buildx imagetools create -t "${image}:${VERSION}${suffix}" "${refs[@]}"
docker buildx imagetools create "${tags[@]}" "${refs[@]}"
}
create_manifest "${IMAGE_NAME}" "" /tmp/digests/base
@@ -437,12 +441,16 @@ jobs:
create_manifest "${IMAGE_NAME}" "-bun" /tmp/digests/bun-base optional
create_manifest "${IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web optional
- name: Create GHCR version manifests
- name: Create GHCR manifest
run: |
set -euo pipefail
create_manifest() {
local image="$1" suffix="$2" dir="$3" optional="${4:-}"
local tags=(-t "${image}:${VERSION}${suffix}")
if [ "$PROMOTE_LATEST" = "true" ]; then
tags+=(-t "${image}:latest${suffix}")
fi
local refs=()
while IFS= read -r digest_file; do
refs+=("${image}@sha256:$(basename "$digest_file")")
@@ -455,7 +463,7 @@ jobs:
echo "No image digests in $dir" >&2
exit 1
fi
docker buildx imagetools create -t "${image}:${VERSION}${suffix}" "${refs[@]}"
docker buildx imagetools create "${tags[@]}" "${refs[@]}"
}
create_manifest "${GHCR_IMAGE_NAME}" "" /tmp/digests/base
@@ -463,59 +471,6 @@ jobs:
create_manifest "${GHCR_IMAGE_NAME}" "-bun" /tmp/digests/bun-base optional
create_manifest "${GHCR_IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web optional
- name: Smoke-test published Docker image
if: needs.prepare.outputs.version != 'main'
run: |
set -euo pipefail
container="omniroute-smoke-${VERSION//[^a-zA-Z0-9_.-]/-}"
trap 'docker rm -f "$container" >/dev/null 2>&1 || true' EXIT
docker run --detach --name "$container" "${IMAGE_NAME}:${VERSION}"
for attempt in $(seq 1 30); do
status="$(docker inspect --format '{{.State.Health.Status}}' "$container")"
if [ "$status" = "healthy" ]; then
exit 0
fi
if [ "$status" = "unhealthy" ] || [ "$(docker inspect --format '{{.State.Status}}' "$container")" = "exited" ]; then
docker logs "$container"
exit 1
fi
sleep 2
done
docker logs "$container"
exit 1
- name: Promote Docker Hub latest tags
if: needs.prepare.outputs.promote_latest == 'true'
run: |
set -euo pipefail
promote_tag() {
local suffix="$1"
docker buildx imagetools create -t "${IMAGE_NAME}:latest${suffix}" "${IMAGE_NAME}:${VERSION}${suffix}"
}
promote_tag ""
promote_tag "-web"
for suffix in -bun -web-bun; do
if docker buildx imagetools inspect "${IMAGE_NAME}:${VERSION}${suffix}" >/dev/null 2>&1; then
promote_tag "$suffix"
fi
done
- name: Promote GHCR latest tags
if: needs.prepare.outputs.promote_latest == 'true'
run: |
set -euo pipefail
promote_tag() {
local suffix="$1"
docker buildx imagetools create -t "${GHCR_IMAGE_NAME}:latest${suffix}" "${GHCR_IMAGE_NAME}:${VERSION}${suffix}"
}
promote_tag ""
promote_tag "-web"
for suffix in -bun -web-bun; do
if docker buildx imagetools inspect "${GHCR_IMAGE_NAME}:${VERSION}${suffix}" >/dev/null 2>&1; then
promote_tag "$suffix"
fi
done
- name: Inspect image
if: needs.prepare.outputs.version != 'main'
run: |
@@ -580,7 +535,7 @@ jobs:
- name: Upload Trivy SARIF to Security tab
if: needs.prepare.outputs.version != 'main'
continue-on-error: true
uses: github/codeql-action/upload-sarif@v4.38.0
uses: github/codeql-action/upload-sarif@v4.37.9
with:
sarif_file: trivy-results.sarif
category: trivy-image

View File

@@ -293,9 +293,6 @@ jobs:
# #8781: open-sse workspace typecheck gate — the workspace imports @/ which
# escapes to src/ via undeclared path aliases. See check-open-sse-typecheck.mjs.
open-sse-typecheck
# Hard Rule #16 — AI/bot attribution in PR commits, title or body (#14436). Reads the PR
# from GITHUB_EVENT_PATH; no-op on non-PR events. ci.yml only runs on PRs to main.
ai-attribution
)
ratchet_gates=(
secrets vuln-ratchet workflows openapi-breaking

View File

@@ -1,42 +0,0 @@
name: Release acceptance
on:
push:
branches: ["release/v*"]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: release-acceptance-${{ github.ref }}
cancel-in-progress: false
jobs:
acceptance:
name: Release acceptance
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: "22"
cache: npm
- run: npm ci
- name: Emit shadow acceptance report
run: |
node scripts/quality/validate-release-acceptance.mjs \
--plan tests/fixtures/release-acceptance/plan-lint.json \
--manifests tests/fixtures/release-acceptance/shadow-manifests \
--out release-acceptance-report.json
continue-on-error: true
- uses: actions/upload-artifact@v4
if: always()
with:
name: release-acceptance-report
path: release-acceptance-report.json
if-no-files-found: ignore
retention-days: 30

2
.gitignore vendored
View File

@@ -74,7 +74,6 @@ yarn-error.log*
# Local gitleaks artifacts (do not commit)
gitleaks-local.json
!.env.example
!.env.selfhost.example
!.env.homolog.example
!.env.devin-bridge.example
# Provider API keys (never commit)
@@ -219,7 +218,6 @@ scripts/i18n/_pending-keys.json
# PR Reviews and local feedback files
pr_reviews*.json
/review/
#hidden local data directories (never commit)
.local-data/

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
# Hard Rule #16 — no AI/bot Co-Authored-By trailers or AI-generation footers in commit metadata.
# Human co-authors stay. Incident record: #14436.
node scripts/check/check-ai-attribution.mjs --message-file "$1"

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,3 @@
# wasm-bindgen glue + embedded WASM_BASE64. Prettier rewrites the generated JS
# (quotes, wrapping) on any touch of this file; format tinycmsDomMocks.ts instead.
open-sse/executors/tinycmsSigner.ts
# Long reference tables are manually aligned; formatting the whole file causes noisy diffs.
docs/reference/ENVIRONMENT.md

View File

@@ -23,7 +23,7 @@
"scripts": {
"build": "tsup",
"clean": "rm -rf dist",
"test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/telemetry.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/feature-defaults.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts tests/auto-combo-context.test.ts tests/provider-id-routing.test.ts tests/management-read-token.test.ts tests/auto-sync.test.ts tests/model-allowlist.test.ts tests/log-level.test.ts tests/effort-tier-variants.test.ts tests/naming.test.ts tests/free-budget-magnitude.test.ts tests/models-fetcher.test.ts tests/issue-13000-cold-start-combo-limit.test.ts",
"test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/telemetry.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/feature-defaults.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts tests/auto-combo-context.test.ts tests/provider-id-routing.test.ts tests/management-read-token.test.ts tests/auto-sync.test.ts tests/model-allowlist.test.ts tests/log-level.test.ts tests/effort-tier-variants.test.ts tests/naming.test.ts tests/free-budget-magnitude.test.ts tests/models-fetcher.test.ts",
"prepublishOnly": "npm run clean && npm run build && npm test"
},
"keywords": [

View File

@@ -4631,21 +4631,9 @@ export function buildStaticProviderEntry(
.map((m) => m.max_output_tokens)
.filter((v): v is number => typeof v === "number" && v > 0);
// Prefer the server-computed aggregate (accounts for explicit
// context_length overrides and members outside memberEntries, e.g.
// not yet resolved in /v1/models) over the raw Math.min(member)
// lower bound. Mirrors mapComboToModelV2's limit.context logic
// (#13000) so the static catalog and the dynamic hook agree.
const preferredContext =
typeof combo.computed_context_length === "number" && combo.computed_context_length > 0
? combo.computed_context_length
: contextValues.length > 0
? Math.min(...contextValues)
: undefined;
if (preferredContext !== undefined && outputValues.length > 0) {
if (contextValues.length > 0 && outputValues.length > 0) {
entry.limit = {
context: preferredContext,
context: Math.min(...contextValues),
output: Math.min(...outputValues),
};
}
@@ -5523,32 +5511,6 @@ export function createOmniRouteConfigHook(
const modelsFetchOk = !modelsFetchThrew && localRawModels.length > 0;
// Snapshot backfill for computed_context_length: a live /api/combos
// response can come back without this field (server hasn't finished
// recomputing it yet, e.g. just after a restart) even though the
// combo's members and identity are otherwise unchanged. When that
// happens, prefer the last-known-good value from the warm disk
// snapshot over the Math.min(member) fallback in
// mapComboToModelV2() — never overwrite any other combo field
// (models/name/etc.) with stale data, only this one derived number.
if (warmSnapshot) {
const snapshotComboById = new Map(warmSnapshot.rawCombos.map((c) => [c.id, c]));
for (const combo of localRawCombos) {
const hasLive =
typeof combo.computed_context_length === "number" &&
combo.computed_context_length > 0;
if (hasLive) continue;
const stale = snapshotComboById.get(combo.id);
if (
stale &&
typeof stale.computed_context_length === "number" &&
stale.computed_context_length > 0
) {
combo.computed_context_length = stale.computed_context_length;
}
}
}
// Disk-cache fallback (cold first run, no warm snapshot): when the
// live fetch returned no models AND features.diskCache !== false,
// hydrate from the last-known-good snapshot so OC still surfaces a
@@ -5556,17 +5518,9 @@ export function createOmniRouteConfigHook(
if (modelsFetchThrew && wantDiskCache && !warmSnapshot) {
const snapshot = await diskSnapshotReader(resolved.providerId, snapshotFingerprint);
if (snapshot && snapshot.rawModels.length > 0) {
// Report snapshot age like the warm-startup path already does:
// "stale" alone reads as a transient blip, so a week-old catalog
// is indistinguishable from a five-minute-old one.
const snapshotAge = snapshot.writtenAt;
const snapshotAgeLabel =
typeof snapshotAge === "number"
? `${Math.round((Date.now() - snapshotAge) / 3_600_000)}h`
: "unknown";
logAt(
"warn",
`config shim: /v1/models unreachable; using stale disk cache (${snapshot.rawModels.length} models, age ${snapshotAgeLabel})`
`config shim: /v1/models unreachable; using stale disk cache (${snapshot.rawModels.length} models)`
);
localRawModels = snapshot.rawModels;
localRawCombos = snapshot.rawCombos;

View File

@@ -481,7 +481,10 @@ test("config: combos fetcher throws → emit models-only catalog (no combos in m
];
assert.ok(entry);
const ids = Object.keys(entry.models).sort();
assert.deepEqual(ids, ["claude-sonnet-4-6", "gemini-3-flash"]);
assert.deepEqual(ids, [
"claude-sonnet-4-6",
"gemini-3-flash",
]);
assert.equal(entry.models["claude-tier"], undefined, "no combo entry");
assert.ok(
logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")),
@@ -1038,7 +1041,11 @@ test("config: features.enrichment=false skips enrichment fetch + keeps raw-id na
];
assert.ok(entry);
assert.equal(enrichmentFetcher.callCount(), 0, "enrichment fetch suppressed by feature flag");
assert.equal(entry.models["claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id retained");
assert.equal(
entry.models["claude-sonnet-4-6"].name,
"claude-sonnet-4-6",
"raw id retained"
);
});
test("config: enrichment fetcher throws → soft-fail (warn + raw-id static catalog)", async () => {
@@ -1061,7 +1068,11 @@ test("config: enrichment fetcher throws → soft-fail (warn + raw-id static cata
"opencode-omniroute"
];
assert.ok(entry, "static block still published on enrichment failure");
assert.equal(entry.models["claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id retained");
assert.equal(
entry.models["claude-sonnet-4-6"].name,
"claude-sonnet-4-6",
"raw id retained"
);
assert.equal(enrichmentFetcher.callCount(), 1);
assert.ok(
logger.entries.some((e) => String(e[0]).includes("/api/pricing/models fetch failed")),
@@ -1259,7 +1270,10 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
assert.ok(entry.models["claude-sonnet-4-6"], "stale snapshot hydrated into static block");
assert.ok(
entry.models["claude-sonnet-4-6"],
"stale snapshot hydrated into static block"
);
assert.equal(
entry.models["claude-sonnet-4-6"].name,
"Claude Sonnet 4.6 (cached)",
@@ -1267,95 +1281,14 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
);
assert.equal(writes, 0, "disk write skipped when live fetch failed");
assert.ok(
logger.entries.some(
(e) =>
String(e[0]).includes("using stale disk cache") ||
String(e[0]).includes("warm startup from disk snapshot")
logger.entries.some((e) =>
String(e[0]).includes("using stale disk cache") ||
String(e[0]).includes("warm startup from disk snapshot")
),
"disk-cache hydration breadcrumb emitted"
);
});
// The stale-fallback branch (`modelsFetchThrew && wantDiskCache && !warmSnapshot`)
// only runs when the warm-startup read found nothing — a snapshot can appear on
// disk between that first read and the live fetch failing (e.g. another OC
// process instance wrote one concurrently). A stateful reader simulates that:
// empty on the warm-startup read, populated by the time the fallback re-reads.
function emptyThenSnapshotReader(
snapshot: Omit<
Awaited<ReturnType<typeof import("../src/index.js").defaultDiskSnapshotReader>> & object,
never
>
): typeof import("../src/index.js").defaultDiskSnapshotReader {
let calls = 0;
return (async () => {
calls++;
return calls === 1 ? undefined : snapshot;
}) as typeof import("../src/index.js").defaultDiskSnapshotReader;
}
test("config: stale-fallback warning reports the disk snapshot age in hours", async () => {
const readAuthJson = stubReadAuthJson({
"opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
});
const fetcher = throwingModelsFetcher();
const combosFetcher = stubCombosFetcher([]);
const logger = captureWarn();
const writtenAt = Date.now() - 2 * 3_600_000; // 2h old
const diskSnapshotReader = emptyThenSnapshotReader({
rawModels: [MODEL_CLAUDE],
rawCombos: [],
rawEnrichment: new Map(),
rawCompressionCombos: [],
rawConnections: [],
writtenAt,
});
const hook = createOmniRouteConfigHook(
{ providerId: "omniroute", features: { diskCache: true } },
{ readAuthJson, fetcher, combosFetcher, diskSnapshotReader, logger }
);
await hook(makeInput());
assert.ok(
logger.entries.some((e) => String(e[0]).includes("using stale disk cache (1 models, age 2h)")),
"stale-fallback warning includes the computed snapshot age"
);
});
test('config: stale-fallback warning falls back to "unknown" age without writtenAt', async () => {
const readAuthJson = stubReadAuthJson({
"opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
});
const fetcher = throwingModelsFetcher();
const combosFetcher = stubCombosFetcher([]);
const logger = captureWarn();
const diskSnapshotReader = emptyThenSnapshotReader({
rawModels: [MODEL_CLAUDE],
rawCombos: [],
rawEnrichment: new Map(),
rawCompressionCombos: [],
rawConnections: [],
});
const hook = createOmniRouteConfigHook(
{ providerId: "omniroute", features: { diskCache: true } },
{ readAuthJson, fetcher, combosFetcher, diskSnapshotReader, logger }
);
await hook(makeInput());
assert.ok(
logger.entries.some((e) =>
String(e[0]).includes("using stale disk cache (1 models, age unknown)")
),
'stale-fallback warning falls back to "unknown" when writtenAt is absent'
);
});
test("config: cached rawEnrichment from earlier provider hook is reused (no refetch)", async () => {
const readAuthJson = stubReadAuthJson({
"opencode-omniroute": { type: "api", key: "sk-shared", baseURL: "https://or.example/v1" },
@@ -1443,7 +1376,10 @@ test("config: providerTag (default-on) prepends '<provider> - ' to enriched raw-
"opencode-omniroute"
];
assert.ok(entry);
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
assert.equal(
entry.models["claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
assert.equal(entry.models["gemini-3-flash"].name, "Gemini - Gemini 3 Flash");
// Combos stay untouched — `Combo: ` prefix already conveys multi-upstream.
assert.equal(entry.models["claude-tier"].name, "Claude Tier");
@@ -1559,7 +1495,10 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
const entryA = (inputA as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
assert.equal(entryA.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
assert.equal(
entryA.models["claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
// Second invocation (cache hit) — name must still be single-suffixed.
const inputB = makeInput();
@@ -1567,7 +1506,10 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
const entryB = (inputB as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
assert.equal(entryB.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
assert.equal(
entryB.models["claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
});
// ────────────────────────────────────────────────────────────────────────────

View File

@@ -1,221 +0,0 @@
/**
* Repro for #13000: combo context limits fall back to Math.min(member)
* instead of using computed_context_length after cold start — no disk
* snapshot fallback.
*
* Scenario (mirrors the report): a warm disk snapshot holds the combo with
* its correct server-computed `computed_context_length` (245000, from all 6
* members). After a restart, the live refresh's combos fetch returns the
* SAME combo but without `computed_context_length` (e.g. the value hasn't
* propagated yet), and the live models fetch only resolves 2 of the 6
* members (the rest not yet in /v1/models). The background refresh then
* republishes the provider block built from this degraded live data,
* downgrading a previously-known-good 245000 limit to Math.min(163840,
* 1_000_000) = 163840 — exactly the member-minimum described in the issue.
*/
import test from "node:test";
import assert from "node:assert/strict";
import type { Config } from "@opencode-ai/plugin";
import {
createOmniRouteConfigHook,
_resetInflightRefresh,
type OmniRouteAutoCombosFetcher,
type OmniRouteCombosFetcher,
type OmniRouteCompressionMetaFetcher,
type OmniRouteEnrichmentFetcher,
type OmniRouteFetchCache,
type OmniRouteModelsFetcher,
type OmniRouteProvidersFetcher,
type OmniRouteRawCombo,
type OmniRouteRawModelEntry,
type OmniRouteReadAuthJson,
type OmniRouteStaticProviderEntry,
type OmniRouteDiskSnapshotReader,
type OmniRouteDiskSnapshotWriter,
} from "../src/index.js";
test.beforeEach(() => {
_resetInflightRefresh();
});
function stubReadAuthJson(value: Record<string, unknown>): OmniRouteReadAuthJson {
return async () => value as never;
}
function authStub() {
return stubReadAuthJson({
"opencode-omniroute": {
type: "api",
key: "sk-test",
baseURL: "https://or.example.com/v1",
},
});
}
function makeInput(): Config {
return { provider: {} } as unknown as Config;
}
// The two members resolvable in the degraded live /v1/models response.
const MEMBER_DEEPSEEK: OmniRouteRawModelEntry = {
id: "deepseek-v4-pro",
capabilities: { tool_calling: true, reasoning: true, vision: false, thinking: false },
context_length: 163_840,
max_output_tokens: 64_000,
input_modalities: ["text"],
output_modalities: ["text"],
};
const MEMBER_GLM: OmniRouteRawModelEntry = {
id: "glm-5.2",
capabilities: { tool_calling: true, reasoning: true, vision: false, thinking: false },
context_length: 1_000_000,
max_output_tokens: 16_384,
input_modalities: ["text"],
output_modalities: ["text"],
};
// The other member that IS present once the server is fully warm.
const MEMBER_GLM_53_HIGH: OmniRouteRawModelEntry = {
id: "GLM-5.3-high",
capabilities: { tool_calling: true, reasoning: true, vision: false, thinking: false },
context_length: 245_000,
max_output_tokens: 128_000,
input_modalities: ["text"],
output_modalities: ["text"],
};
const COMBO_MODELS: OmniRouteRawCombo["models"] = [
{ kind: "model", model: "deepseek-v4-pro", weight: 25 },
{ kind: "model", model: "glm-5.2", weight: 25 },
{ kind: "model", model: "GLM-5.3-high", weight: 50 },
];
test("issue #13000: warm combo limit (245000) survives a degraded post-restart refresh instead of downgrading to Math.min(member)", async () => {
const warmSnapshot: Omit<import("../src/index.js").OmniRouteFetchCacheEntry, "expiresAt"> = {
rawModels: [MEMBER_DEEPSEEK, MEMBER_GLM, MEMBER_GLM_53_HIGH],
rawCombos: [
{
id: "orchestrator",
name: "orchestrator",
models: COMBO_MODELS,
computed_context_length: 245_000,
},
],
rawAutoCombos: [],
rawEnrichment: new Map(),
rawCompressionCombos: [],
rawConnections: [],
};
const fetcher: OmniRouteModelsFetcher = async () => [MEMBER_DEEPSEEK, MEMBER_GLM];
const combosFetcher: OmniRouteCombosFetcher = async () => [
{
id: "orchestrator",
name: "orchestrator",
models: COMBO_MODELS,
// computed_context_length intentionally omitted.
},
];
const autoCombosFetcher: OmniRouteAutoCombosFetcher = async () => [];
const enrichmentFetcher: OmniRouteEnrichmentFetcher = async () => new Map();
const compressionMetaFetcher: OmniRouteCompressionMetaFetcher = async () => [];
const providersFetcher: OmniRouteProvidersFetcher = async () => [];
const diskSnapshotReader: OmniRouteDiskSnapshotReader = async () => warmSnapshot;
const diskSnapshotWriter: OmniRouteDiskSnapshotWriter = async () => {};
const sharedCache: OmniRouteFetchCache = new Map();
const hook = createOmniRouteConfigHook(
{ providerId: "omniroute", modelCacheTtl: 60_000 },
{
readAuthJson: authStub(),
fetcher,
combosFetcher,
autoCombosFetcher,
enrichmentFetcher,
compressionMetaFetcher,
providersFetcher,
diskSnapshotReader,
diskSnapshotWriter,
cache: sharedCache,
}
);
const input = makeInput();
await hook(input);
// Let the detached background refresh (degraded live data) complete and
// republish the block.
await new Promise((r) => setTimeout(r, 100));
const entryAfter = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
const comboModelAfter = entryAfter.models["orchestrator"];
assert.ok(comboModelAfter, "combo model still published after refresh");
assert.equal(
comboModelAfter.limit.context,
245_000,
`expected the combo limit to stay at the known-good 245000, but got ${comboModelAfter.limit.context} ` +
`(Math.min(member) fallback — the exact bug described in #13000)`
);
});
test("issue #13000 (control): no warm snapshot exists — Math.min(member) fallback is still used (expected, documented behavior)", async () => {
const fetcher: OmniRouteModelsFetcher = async () => [MEMBER_DEEPSEEK, MEMBER_GLM];
const combosFetcher: OmniRouteCombosFetcher = async () => [
{
id: "orchestrator",
name: "orchestrator",
models: COMBO_MODELS,
// computed_context_length intentionally omitted.
},
];
const autoCombosFetcher: OmniRouteAutoCombosFetcher = async () => [];
const enrichmentFetcher: OmniRouteEnrichmentFetcher = async () => new Map();
const compressionMetaFetcher: OmniRouteCompressionMetaFetcher = async () => [];
const providersFetcher: OmniRouteProvidersFetcher = async () => [];
// No prior snapshot on disk.
const diskSnapshotReader: OmniRouteDiskSnapshotReader = async () => undefined;
const diskSnapshotWriter: OmniRouteDiskSnapshotWriter = async () => {};
const sharedCache: OmniRouteFetchCache = new Map();
const hook = createOmniRouteConfigHook(
{ providerId: "omniroute", modelCacheTtl: 60_000 },
{
readAuthJson: authStub(),
fetcher,
combosFetcher,
autoCombosFetcher,
enrichmentFetcher,
compressionMetaFetcher,
providersFetcher,
diskSnapshotReader,
diskSnapshotWriter,
cache: sharedCache,
}
);
const input = makeInput();
await hook(input);
const entryAfter = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
const comboModelAfter = entryAfter.models["orchestrator"];
assert.ok(comboModelAfter, "combo model published on cold first run");
// No snapshot to backfill from — Math.min(163840, 1_000_000) = 163840.
assert.equal(
comboModelAfter.limit.context,
163_840,
"pure cold start with no snapshot must keep using the Math.min(member) fallback"
);
});

View File

@@ -12,7 +12,7 @@
> // opencode.json
> {
> "$schema": "https://opencode.ai/config.json",
> "plugin": ["@omniroute/opencode-plugin"],
> "plugin": ["@omniroute/opencode-plugin"]
> }
> ```
>
@@ -100,7 +100,7 @@ Returns the value to place under `provider.omniroute` inside `opencode.json`.
| `baseURL` | `string` | Yes | OmniRoute base URL. Accepts `http://host:port` **or** `http://host:port/v1`. Trailing slashes are tolerated. |
| `apiKey` | `string` | Yes | OmniRoute API key. Use `sk_omniroute` for local installs that have `REQUIRE_API_KEY=false`. |
| `displayName` | `string` | No | Custom name shown in the OpenCode UI. Default: `"OmniRoute"`. |
| `models` | `string[]` | No | Override the surfaced model catalog. Default: 8 curated models — see `OMNIROUTE_DEFAULT_OPENCODE_MODELS`. |
| `models` | `string[]` | No | Override the surfaced model catalog. Default: 4 curated models — see `OMNIROUTE_DEFAULT_OPENCODE_MODELS`. |
| `modelLabels` | `Record<string,string>` | No | Human-readable labels keyed by model id. |
Throws on empty/invalid input — `baseURL` must be a real URL, `apiKey` must be a non-empty string.
@@ -143,7 +143,7 @@ Duplicates and empty strings are dropped automatically, and order is preserved.
- **Requests 404 with `/v1/v1/...`** — you're on an old version (≤1.0.0). Update to `≥0.1.0` of this re-released package. The new build normalises `baseURL` automatically.
- **`401 Invalid API key`** — your OmniRoute instance has `REQUIRE_API_KEY=true` but the key you supplied doesn't exist there. Create one via the dashboard or set `REQUIRE_API_KEY=false` and use `sk_omniroute`.
- **OpenCode complains the provider has no models** — supply an explicit `models` list; the default 8 may be hidden by your provider visibility settings.
- **OpenCode complains the provider has no models** — supply an explicit `models` list; the default 4 may be hidden by your provider visibility settings.
## Related

View File

@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
## Project at a Glance
**OmniRoute** — unified AI proxy/router. One endpoint, 360 LLM providers, auto-fallback.
**OmniRoute** — unified AI proxy/router. One endpoint, 358 LLM providers, auto-fallback.
| Layer | Location | Purpose |
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -56,7 +56,7 @@ Repository map and Reference Documentation sections below.
| Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) |
| Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions |
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
| Database | `src/lib/db/` | SQLite domain modules (178 migrations) |
| Database | `src/lib/db/` | SQLite domain modules (176 migrations) |
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
| MCP Server | `open-sse/mcp-server/` | 110 tools (45 canonical + memory/skill/GitHub/pool/gamification/plugin/Notion/Obsidian/local-corpus/RTK modules), 3 transports (stdio / SSE / Streamable HTTP), 33 scopes |
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
@@ -542,7 +542,6 @@ git push -u origin feat/your-feature
**Husky hooks**:
- **pre-commit**: lint-staged + `check-docs-sync` + `check:any-budget:t11` + `check:tracked-artifacts`
- **commit-msg**: `check:ai-attribution` — rejects AI/bot `Co-Authored-By` trailers and AI-generation footers in the message (Hard Rule #16; human co-authors allowed; also in the `quality.yml` fast-gates loop (PR→`release/**`) and a PR-only `ci.yml` lint step (PR→`main`) — #14436)
- **pre-push**: intentionally light (PATH/npm sanity only). `any-budget` + `tracked-artifacts`
already run on pre-commit; re-running them on every push was pure double-pay. CI still
enforces both. (Was Fase 6A.12 full pre-push gate; folded into pre-commit in #6716.)
@@ -665,7 +664,7 @@ focused checks, and use a Conventional Commit message (for example, `docs: slim
## Environment
- **Runtime**: Node.js ≥22.22.2 <23 || ≥24.0.0 <27, ES Modules. This is the **only supported** runtime for the published `omniroute` CLI, the server, and the test suites (`node:test` + vitest) — `engines.node` is authoritative and end users never need Bun. A **best-effort `bun:sqlite` compatibility path** exists so a global Bun install (`bun install -g omniroute`) can start without `better-sqlite3` (driver adapter + Bun-aware process spawning); it is **not** a supported runtime — no support guarantees — and every Bun-specific runtime change MUST preserve the Node driver/fallback chain and ship a Bun test (`test:bun:db`) or an explicit reason why the path is Node-only.
- **Bun (build/dev script runner + compatibility smoke only)**: Bun `1.4.2` is pinned as an **exact devDependency** (provisioned through the existing `npm ci` via the lockfile's `@oven/bun-*` platform binaries — no `setup-bun`/ad-hoc install). It is used **only** to execute a small, allow-listed set of TypeScript **gate/generator scripts** (replacing `node --import tsx` for startup speed): the CI checks `check:provider-consistency`, `check:compression-budget`, `check:known-symbols`, and the non-CI `gen:provider-reference`, `bench:compression` — plus the focused `test:bun:db` compatibility smoke suite for the best-effort `bun:sqlite` path. **Do NOT** widen Bun to `npm install`, the build (`build:cli*`), `check:pack-artifact`, the supported published runtime, or the main test runners — those stay on Node. Any new Bun-invoking gate/generator script must be validated byte-identical against its `node --import tsx` output first. After pulling the lockfile change, run `npm install` so `bun` resolves locally (a stale `node_modules` will fail those scripts with `bun: not found`).
- **Bun (build/dev script runner + compatibility smoke only)**: Bun `1.4.0` is pinned as an **exact devDependency** (provisioned through the existing `npm ci` via the lockfile's `@oven/bun-*` platform binaries — no `setup-bun`/ad-hoc install). It is used **only** to execute a small, allow-listed set of TypeScript **gate/generator scripts** (replacing `node --import tsx` for startup speed): the CI checks `check:provider-consistency`, `check:compression-budget`, `check:known-symbols`, and the non-CI `gen:provider-reference`, `bench:compression` — plus the focused `test:bun:db` compatibility smoke suite for the best-effort `bun:sqlite` path. **Do NOT** widen Bun to `npm install`, the build (`build:cli*`), `check:pack-artifact`, the supported published runtime, or the main test runners — those stay on Node. Any new Bun-invoking gate/generator script must be validated byte-identical against its `node --import tsx` output first. After pulling the lockfile change, run `npm install` so `bun` resolves locally (a stale `node_modules` will fail those scripts with `bun: not found`).
- **TypeScript**: 6.0+, target ES2022, module esnext, resolution bundler
- **Path aliases**: `@/*` → `src/`, `@omniroute/open-sse` → `open-sse/`, `@omniroute/open-sse/*` → `open-sse/*`
- **Default port**: 20128 (API + dashboard on same port)

File diff suppressed because it is too large Load Diff

View File

@@ -106,8 +106,7 @@ RUN test -f package-lock.json \
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-npm-cache,target=/root/.npm \
npm ci --include=optional --no-audit --no-fund --legacy-peer-deps --ignore-scripts \
&& (cd node_modules/better-sqlite3 \
&& node /usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js rebuild --force_build=1) \
&& test -f node_modules/better-sqlite3/build/Release/better_sqlite3.node \
&& node /usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js rebuild) \
&& node -e "require('better-sqlite3')(':memory:').close()" \
&& node -e "const wreq=require('wreq-js'); if(typeof wreq.createTransport!=='function') process.exit(1)"
@@ -226,19 +225,7 @@ ENV NODE_OPTIONS="--max-old-space-size=${OMNIROUTE_MEMORY_MB}"
# Data directory inside Docker — must match the volume mount in docker-compose.yml
ENV DATA_DIR=/app/data
RUN mkdir -p /app/data && chown node:node /app /app/data
# #13679: default the PUBLISHED image to requiring an API key. A bare
# `docker run -p 20128:20128 … diegosouzapw/omniroute` (README/QUICK-START
# one-liners) does not pass `--env-file .env`, so without this default the
# anonymous /v1 LLM proxy would be both keyless AND world-reachable on the
# published container. This does NOT change the npm/CLI local-dev default
# (`REQUIRE_API_KEY` stays `"false"` in featureFlagDefinitions.ts) — only the
# shipped deployment artifact's posture. docker-compose.yml is unaffected: it
# loads the operator's own `.env` (env_file:) which overrides this ENV, and
# already binds loopback-only by default (#12568). Override with
# `-e REQUIRE_API_KEY=false` for an intentionally keyless deployment.
ENV REQUIRE_API_KEY=true
RUN mkdir -p /app/data
# `npm run build` (build-next-isolated → assembleStandalone) bundles ALL runtime
# files into .build/next/standalone/ — .next, node_modules, migrations, scripts,
@@ -248,24 +235,23 @@ ENV REQUIRE_API_KEY=true
# The old per-module overrides were therefore pure duplication and were removed
# (build-output-isolation cleanup). See scripts/build/assembleStandalone.mjs
# (EXTRA_MODULE_ENTRIES) for the single source of truth.
COPY --chown=node:node --from=builder /app/.build/next/standalone ./
COPY --from=builder /app/.build/next/standalone ./
# better-sqlite3 is the one exception still copied explicitly: assembleStandalone
# only syncs its native build/ dir; the JS wrapper (lib/, package.json) is left to
# Next.js tracing. bootstrap-env requires SQLite BEFORE the standalone server
# starts, so guarantee the complete package independent of trace behaviour.
COPY --chown=node:node --from=builder /app/node_modules/better-sqlite3 ./node_modules/better-sqlite3
RUN test -f /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node
COPY --from=builder /app/node_modules/better-sqlite3 ./node_modules/better-sqlite3
# migrations land at <standalone>/migrations via assembleStandalone; point the runtime at them.
ENV OMNIROUTE_MIGRATIONS_DIR=/app/migrations
# Docker healthcheck script — not traced by Next.js standalone output, so copy
# it explicitly. The HEALTHCHECK CMD references it as `node healthcheck.mjs`.
COPY --chown=node:node --from=builder /app/scripts/dev/healthcheck.mjs ./healthcheck.mjs
COPY --from=builder /app/scripts/dev/healthcheck.mjs ./healthcheck.mjs
# Every COPY above hands its files to the baked-in `node` non-root user
# (UID/GID 1000) at copy time. Do NOT add a `RUN chown -R node:node /app`
# afterwards: in the overlay filesystem changing ownership rewrites every file
# into a new layer, which stored the ~2 GB standalone build twice (#13990).
# Hand /app over to the baked-in `node` non-root user (UID/GID 1000) so the
# runtime process never holds root privileges. The chown happens after all
# COPYs so it covers files originally owned by root in the builder stage.
RUN chown -R node:node /app
EXPOSE 20128
@@ -354,7 +340,7 @@ RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-cache,targe
# build, not the floating `@latest`.
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-npm-cache,target=/root/.npm \
npm install -g --no-audit --no-fund \
@openai/codex@0.155.0 \
@openai/codex@0.153.4 \
@anthropic-ai/claude-code@2.1.260 \
droid@0.212.0 \
openclaw@2026.9.1

View File

@@ -1,5 +1,5 @@
# ── Multi-stage Dockerfile for Native Bun Runtime (web-latest-bun) ───────────
FROM oven/bun:1.4.2-slim AS base
FROM oven/bun:1.4.0-slim AS base
WORKDIR /app
RUN apt-get update \
@@ -56,7 +56,7 @@ ENV NODE_ENV=production
RUN bun run --quiet build
# ── Runner Base stage (100% Bun Native Production Runtime) ──────────────────
FROM oven/bun:1.4.2-slim AS runner-base
FROM oven/bun:1.4.0-slim AS runner-base
LABEL org.opencontainers.image.title="omniroute" \
org.opencontainers.image.description="Unified AI proxy — route any LLM through one endpoint (Bun Native)" \

View File

@@ -7,19 +7,19 @@
# 🚀 OmniRoute — The Free AI Gateway
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 360 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 360 AI providers · 150+ free tiers · ~1.62B free tokens/mo · 19 routing strategies · $0 to start."/>
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 358 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 358 AI providers · 150+ free tiers · ~1.47B free tokens/mo · 19 routing strategies · $0 to start."/>
</div>
<div align="center">
## 💰 ~1.62B Free Tokens / Month
## 💰 ~1.47B Free Tokens / Month
</div>
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute catalogs **489 free-tier entries across 35 recurring pool keys** and computes the token headline from the **17 pools with a published positive monthly budget plus five per-model Groq caps**, deduplicated by shared pool. Quotas that only open after a regional identity check (today: ModelScope) are shown apart, +~6M behind regional identity verification, and never summed into the headline. The result stays visible on the dashboard (`/dashboard/free-tiers`).
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute catalogs **443 free-tier entries across 34 recurring pool keys** and computes the token headline from the **16 pools with a published positive monthly budget plus five per-model Groq caps**, deduplicated by shared pool. Quotas that only open after a regional identity check (today: ModelScope) are shown apart, +~6M behind regional identity verification, and never summed into the headline. The result stays visible on the dashboard (`/dashboard/free-tiers`).
<img src="./docs/diagrams/free-tier-budget.svg" width="100%" alt="OmniRoute free-tier budget card: ~1.62B free tokens per month steady, up to ~2.22B in the first month with signup credits, from 35 documented recurring pool keys covering 489 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 17 recurring pools with a published positive monthly token budget plus five per-model Groq caps; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, Nara 210M, LLM7 150M, xKiro 150M, Groq 30M (five per-model caps) and smaller pools, plus first-month signup credits and permanently-free no-token-cap providers surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers."/>
<img src="./docs/diagrams/free-tier-budget.svg" width="100%" alt="OmniRoute free-tier budget card: ~1.47B free tokens per month steady, up to ~2.07B in the first month with signup credits, from 34 documented recurring pool keys covering 443 cataloged free-tier entries behind one endpoint. Honest pool-deduped math — each shared pool counted once, including 16 recurring pools with a published positive monthly token budget plus five per-model Groq caps; 13 providers are marked avoid in the terms-risk catalog so you decide. Budget bar includes Mistral 1B, Nara 210M, LLM7 150M, Groq 30M (five per-model caps) and smaller pools, plus first-month signup credits and permanently-free no-token-cap providers surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers."/>
> Animated summary of the live `/dashboard/free-tiers` page. Full methodology (pool dedupe, credit tiers, provider terms): **[docs/reference/FREE_TIERS.md](docs/reference/FREE_TIERS.md)**.
>
@@ -63,7 +63,7 @@
| | v3.8.49 | **v3.8.50** | `v3.8.51+` |
| ------------------------- | :-----: | :-----------------------: | :---------: |
| 🌐 Providers | 290 | **357** | more queued |
| 🌐 Providers | 290 | **352** | more queued |
| 🧠 Unique chat model IDs | 1185 | **1312** | — |
| 🖼️ Modality Bridge | — | 🆕 vision + audio + video | — |
| 📡 Radar free catalog | — | 🆕 opt-in | — |
@@ -101,7 +101,7 @@
<tr>
<td align="right"><b>⚙️ Features</b></td>
<td align="center"><a href="#-combos--the-flagship">🎯 Combos</a></td>
<td align="center"><a href="#-357-ai-providers--152-catalog-marked-free">🌐 Providers</a></td>
<td align="center"><a href="#-352-ai-providers--154-catalog-marked-free">🌐 Providers</a></td>
<td align="center"><a href="#-full-cli--a2a--mcp">🔌 CLI &amp; MCP</a></td>
</tr>
<tr>
@@ -233,7 +233,7 @@ curl http://localhost:20128/v1/chat/completions \
</div>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 360 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 360 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 54 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 358 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 358 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 52 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
<br/>
<br/>
@@ -486,7 +486,7 @@ All **19** strategies — mix & match per combo step:
</div>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 360 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 42 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 358 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 42 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<sub>📊 Full methodology &amp; per-feature detail vs 9router, OpenRouter, CLIProxyAPI &amp; LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
@@ -542,9 +542,9 @@ Pix copia-e-cola:
## 📡 OmniRoute Radar
The main free-tier headline remains **~1.62B tokens/month** from the documented,
The main free-tier headline remains **~1.47B tokens/month** from the documented,
pool-deduplicated catalog above. Temporary provider signup credits can separately lift the first
month to **~2.22B**. Radar is an optional, signed catalog overlay for people who want fresher
month to **~2.07B**. Radar is an optional, signed catalog overlay for people who want fresher
free-model availability between OmniRoute releases; the community catalog and every existing free
feature remain free.
@@ -629,13 +629,13 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
<td align="center" width="76"><picture><source media="(prefers-color-scheme:dark)" srcset="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.91.0/dark/goose.png"/><img src="https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@1.91.0/icons/goose.svg" width="40" alt="Goose"/></picture><br/><sub><b>Goose</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="Open Interpreter"/><br/><sub><b>Open Interpreter</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="Warp AI"/><br/><sub><b>Warp AI</b></sub><br/><sub>                           </sub></td>
<td align="center" width="76"><a href="https://deyin.ai"><img src="./public/deyin.svg" width="40" alt="deyin.ai"/><br/><sub><b>deyin.ai</b></sub><br/><sub>                           </sub></a></td>
<td align="center" width="76"><img src="./public/providers/cli-generic.svg" width="40" alt="Agent Deck"/><br/><sub><b>Agent Deck</b></sub><br/><sub>                           </sub></td>
</tr>
</table>
</div>
<div align="center">
<b> also works with</b> · Agent Deck · Kiro · Command Code · Antigravity · Windsurf · AMP · <b>any OpenAI-compatible tool</b>
<b> also works with</b> · Kiro · Command Code · Antigravity · Windsurf · AMP · <b>any OpenAI-compatible tool</b>
</div>
<sub>📖 Per-tool setup for all 36 tools (26 CLI Code's + 10 CLI Agents) → [`docs/reference/CLI-TOOLS.md`](docs/reference/CLI-TOOLS.md) · 🧩 OpenCode plugin → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider)</sub>
@@ -668,11 +668,11 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
<div align="center">
## 🌐 357 AI Providers — 152 Catalog-Marked Free
## 🌐 352 AI Providers — 152 Catalog-Marked Free
</div>
> **357 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **491 per-model rows**, **35 recurring pools** and **54 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
> **352 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **443 per-model rows**, **34 recurring pools** and **52 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
<div align="center">
@@ -862,7 +862,7 @@ with a scoped access token; every command then targets the remote.
```bash
omniroute connect 192.168.0.15 # password → scoped token, saved as a context
omniroute models # ← runs against the REMOTE server
omniroute models list # ← runs against the REMOTE server
omniroute configure codex # ← picks a remote model, writes a local Codex profile
omniroute tokens create --name ci --scope read # mint narrower tokens for other machines
omniroute contexts use default # ← switch back to the local server
@@ -1006,10 +1006,6 @@ omniroute
```
> 💡 See `npm warn ERESOLVE` or peer-dep warnings? [They're harmless](docs/guides/TROUBLESHOOTING.md#npm-install-warnings-eresolve--peer--deprecated).
> **Using Gemini Web or another web-cookie provider?** The npm package includes
> Playwright but not its Chromium binary. See the
> [Playwright Chromium setup](docs/guides/TROUBLESHOOTING.md#gemini-web-and-playwright-chromium)
> note before making the first web-provider request.
Dashboard at `http://localhost:20128` · API at `http://localhost:20128/v1`.
@@ -1152,9 +1148,7 @@ install never blocks on compiling from source: it uses a prebuilt binary when on
your platform/Node, and otherwise falls back transparently to a pure-JS engine
(`node:sqlite` on Node 22+, else the bundled `sql.js` WASM) — no build tools required.
To skip the post-install **native warm-up** entirely (CI, headless, or slow machines).
Note: this only skips the native SQLite warm-up step (`scripts/postinstall.mjs`); the
binary-copy/repair hook (`scripts/build/postinstall.mjs`) still runs normally:
To skip the post-install native warm-up entirely (CI, headless, or slow machines):
```bash
OMNIROUTE_SKIP_POSTINSTALL=1 npm install -g omniroute # CI=1 also skips it
@@ -1274,7 +1268,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
<tr><td nowrap><b>Runtime</b></td><td>Node.js 22.x / 24.x LTS — <code>&gt;=22.22.2 &lt;23 || &gt;=24.0.0 &lt;27</code></td></tr>
<tr><td nowrap><b>Language</b></td><td>TypeScript 6.0 — <b>100% TypeScript</b> across <code>src/</code> and <code>open-sse/</code> (zero <code>any</code> in core since v2.0)</td></tr>
<tr><td nowrap><b>Framework</b></td><td>Next.js 16 + React 19 + Tailwind CSS 4</td></tr>
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 178 migrations</td></tr>
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 176 migrations</td></tr>
<tr><td nowrap><b>Memory</b></td><td>SQLite FTS5 full-text + int8-quantized vector embeddings, typed decay</td></tr>
<tr><td nowrap><b>Schemas</b></td><td>Zod 4 — MCP tool I/O validation + API contracts</td></tr>
<tr><td nowrap><b>Protocols</b></td><td>MCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE)</td></tr>
@@ -1337,7 +1331,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
<tr><td nowrap><b><a href="docs/architecture/RESILIENCE_GUIDE.md">Resilience Guide</a></b></td><td>Circuit breakers, cooldowns, queue, anti-thundering herd, TLS spoofing</td></tr>
<tr><td nowrap><b><a href="docs/routing/AUTO-COMBO.md">Auto-Combo Engine</a></b></td><td>16-factor scoring, mode packs, self-healing</td></tr>
<tr><td nowrap><b><a href="docs/ops/PROXY_GUIDE.md">Proxy Guide</a></b></td><td>3-level proxy system, 1proxy marketplace, registry CRUD</td></tr>
<tr><td nowrap><b><a href="docs/reference/FREE_TIERS.md">Free Tiers</a></b></td><td>Consolidated directory: 35 documented recurring pools / 489 cataloged free-tier entries</td></tr>
<tr><td nowrap><b><a href="docs/reference/FREE_TIERS.md">Free Tiers</a></b></td><td>Consolidated directory: 34 documented recurring pools / 443 cataloged free-tier entries</td></tr>
<tr><td nowrap><b><a href="docs/guides/FEATURES.md">Features Gallery</a></b></td><td>Visual dashboard tour with screenshots</td></tr>
<tr><td nowrap><b><a href="docs/architecture/CODEBASE_DOCUMENTATION.md">Codebase Documentation</a></b></td><td>Beginner-friendly codebase walkthrough</td></tr>
</table>

View File

@@ -218,8 +218,6 @@ These rules are enforced by tooling and reviewers:
## Supply-chain scanner findings (Socket.dev / Snyk / similar)
> **Scope note:** `socket.yml` at the repository root only shapes `projectIgnorePaths` for Socket.dev's registry-side post-publish scan of the published npm artifact — it is not an enforced CI/PR merge gate. No workflow in `.github/workflows`, no `package.json` script, and no `Makefile` target invokes Socket.dev.
The published `omniroute` npm artifact bundles the Next.js `output: "standalone"`
build, which means every route handler — including documented privileged
features (MITM, Zed import, Cloud Sync, embedded service supervisor) — ends

View File

@@ -1,582 +0,0 @@
#!/usr/bin/env node
/**
* OmniRoute Antigravity Bridge Proxy
*
* Intercepts Antigravity CLI and IDE requests:
* - Directs Gemini 3.8 models directly to Google backend (100% native, untouched).
* - Directs other models (Claude Sonnet 4.5/4.6, Opus, Gemini 3.7, GPT-OSS, etc.) to OmniRoute /v1/antigravity.
* - Passes all non-model Google requests (auth, onboarding, telemetry) directly to Google backend.
* - Transparently forwards all other non-target internet traffic.
*/
import net from "node:net";
import http from "node:http";
import https from "node:https";
import tls from "node:tls";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const PORT = parseInt(process.env.BRIDGE_PORT || "20129", 10);
const ROUTER_URL = process.env.ROUTER_URL || "http://127.0.0.1:20128/v1/antigravity";
const ROUTER_API_KEY =
process.env.ROUTER_API_KEY || process.env.OMNIROUTE_API_KEY || "sk-omniroute-bridge-local";
// Connection pool agents with TCP keep-alive
const httpAgent = new http.Agent({
keepAlive: true,
keepAliveMsecs: 60000,
maxSockets: 64,
maxFreeSockets: 16,
timeout: 120000,
});
const httpsAgent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 60000,
maxSockets: 64,
maxFreeSockets: 16,
timeout: 120000,
});
let cachedSslOptions = null;
function getSslOptions() {
if (cachedSslOptions) return cachedSslOptions;
const certDir =
process.env.CERT_DIR || path.join(process.env.HOME || process.cwd(), ".omniroute", "mitm");
const serverKey = path.join(certDir, "server.key");
const serverCrt = path.join(certDir, "server.crt");
if (!fs.existsSync(serverKey) || !fs.existsSync(serverCrt)) {
console.error("❌ Certificate files not found in", certDir);
process.exit(1);
}
cachedSslOptions = {
key: fs.readFileSync(serverKey),
cert: fs.readFileSync(serverCrt),
};
return cachedSslOptions;
}
const TARGET_HOSTS = new Set([
"cloudcode-pa.googleapis.com",
"daily-cloudcode-pa.googleapis.com",
"daily-cloudcode-pa.sandbox.googleapis.com",
"autopush-cloudcode-pa.sandbox.googleapis.com",
"preprod-daily-cloudcode-pa.sandbox.googleapis.com",
"antigravity-unleash.goog",
]);
function isGenerationRequest(url) {
if (!url) return false;
return (
url.includes(":generateContent") ||
url.includes(":streamGenerateContent") ||
url.includes("/GenerateChat") ||
url.includes("/StreamGenerateChat") ||
url.includes("/GenerateCode") ||
url.includes("/CompleteCode")
);
}
function extractModel(body, url) {
if (body && typeof body === "object") {
if (typeof body.model === "string" && body.model) return body.model;
if (body.request && typeof body.request.model === "string" && body.request.model) {
return body.request.model;
}
}
if (url) {
try {
const parsed = new URL(url, "https://cloudcode-pa.googleapis.com");
const m = parsed.searchParams.get("model");
if (m) return m;
} catch {}
}
return null;
}
const MODEL_ROUTING_MAP = {
// Official OmniRoute Auto Groups
"auto/best-fast": "groq/openai/gpt-oss-120b",
"auto/best-coding": "mistral/codestral-latest",
"auto/best-reasoning": "nvidia/nvidia/nemotron-3-super-120b-a12b",
"auto/best-free": "groq/qwen/qwen3.8-27b",
"auto/best-vision": "nvidia/meta/llama-3.2-90b-vision-instruct",
"auto/coding:pro": "mistral/codestral-latest",
"auto/coding:fast": "groq/openai/gpt-oss-120b",
"auto/coding:free": "groq/qwen/qwen3.8-27b",
"auto/coding:reliable": "mistral/codestral-latest",
"auto/reasoning:pro": "nvidia/nvidia/nemotron-3-super-120b-a12b",
"auto/smart": "nvidia/nvidia/nemotron-3-super-120b-a12b",
"auto/claude-sonnet": "mistral/codestral-latest",
"auto/claude-opus": "nvidia/nvidia/nemotron-3-super-120b-a12b",
"auto/gemini": "gemini/gemini-2.5-flash",
"auto/llama": "groq/openai/gpt-oss-120b",
"auto/gemma": "groq/qwen/qwen3.8-27b",
// Human-readable Display Names (in case CLI sends displayName in envelope)
"Auto: Best Fast (OmniRoute)": "groq/openai/gpt-oss-120b",
"Auto: Best Coding (OmniRoute)": "mistral/codestral-latest",
"Auto: Best Reasoning (OmniRoute)": "nvidia/nvidia/nemotron-3-super-120b-a12b",
"Auto: Best Free (OmniRoute)": "groq/qwen/qwen3.8-27b",
"Auto: Best Vision (OmniRoute)": "nvidia/meta/llama-3.2-90b-vision-instruct",
"Auto: Coding Pro (OmniRoute)": "mistral/codestral-latest",
"Auto: Coding Fast (OmniRoute)": "groq/openai/gpt-oss-120b",
"Auto: Coding Free (OmniRoute)": "groq/qwen/qwen3.8-27b",
"Auto: Coding Reliable (OmniRoute)": "mistral/codestral-latest",
"Auto: Reasoning Pro (OmniRoute)": "nvidia/nvidia/nemotron-3-super-120b-a12b",
"Auto: Smart (OmniRoute)": "nvidia/nvidia/nemotron-3-super-120b-a12b",
"Auto: Claude Sonnet (OmniRoute)": "mistral/codestral-latest",
"Auto: Claude Opus (OmniRoute)": "nvidia/nvidia/nemotron-3-super-120b-a12b",
"Auto: Gemini (OmniRoute)": "gemini/gemini-2.5-flash",
"Auto: Llama (OmniRoute)": "groq/openai/gpt-oss-120b",
"Auto: Gemma (OmniRoute)": "groq/qwen/qwen3.8-27b",
// Fail-safe self-healing for dead/retired models
"nvidia/deepseek-ai/deepseek-v4-pro-0813": "groq/openai/gpt-oss-120b",
"deepseek-ai/deepseek-v4-pro-0813": "groq/openai/gpt-oss-120b",
"NVIDIA: DeepSeek V4 Pro": "groq/openai/gpt-oss-120b",
"nvidia/openai/gpt-oss-120b": "groq/openai/gpt-oss-120b",
"openai/gpt-oss-120b": "groq/openai/gpt-oss-120b",
"groq/llama-3.3-70b-versatile": "groq/openai/gpt-oss-120b",
"llama-3.3-70b-versatile": "groq/openai/gpt-oss-120b",
};
function resolveTargetModel(model) {
if (!model) return "groq/openai/gpt-oss-120b";
if (MODEL_ROUTING_MAP[model]) return MODEL_ROUTING_MAP[model];
const clean = model.replace(/^models\//, "").trim();
if (MODEL_ROUTING_MAP[clean]) return MODEL_ROUTING_MAP[clean];
for (const [k, v] of Object.entries(MODEL_ROUTING_MAP)) {
if (k.toLowerCase() === model.toLowerCase() || k.toLowerCase() === clean.toLowerCase()) {
return v;
}
}
if (
clean.includes("deepseek-v4-pro") ||
(clean.startsWith("nvidia") && clean.includes("gpt-oss-120b")) ||
clean.includes("llama-3.3-70b-versatile")
) {
return "groq/openai/gpt-oss-120b";
}
return clean;
}
const OMNIROUTE_BUILTIN_GROUPS = [
{
id: "auto/best-coding",
displayName: "Auto: Best Coding (OmniRoute)",
descriptionText:
"OmniRoute dynamic routing to the highest benchmark coding model available (Mistral Codestral)",
},
{
id: "auto/best-reasoning",
displayName: "Auto: Best Reasoning (OmniRoute)",
descriptionText:
"OmniRoute dynamic routing to the highest benchmark reasoning model available (Nemotron 3 Super 120B)",
},
{
id: "auto/best-fast",
displayName: "Auto: Best Fast (OmniRoute)",
descriptionText: "OmniRoute sub-second lowest latency high-throughput model (Groq LPUs)",
},
{
id: "auto/best-vision",
displayName: "Auto: Best Vision (OmniRoute)",
descriptionText: "OmniRoute multimodal & computer vision routing",
},
{
id: "auto/best-free",
displayName: "Auto: Best Free (OmniRoute)",
descriptionText: "OmniRoute 100% unmetered free tier model routing (Qwen 3.8 27B)",
},
{
id: "auto/coding:pro",
displayName: "Auto: Coding Pro (OmniRoute)",
descriptionText: "OmniRoute frontier pro-tier coding model (Codestral)",
},
{
id: "auto/coding:fast",
displayName: "Auto: Coding Fast (OmniRoute)",
descriptionText: "OmniRoute fast sub-second daily coding model (Groq 120B)",
},
{
id: "auto/coding:free",
displayName: "Auto: Coding Free (OmniRoute)",
descriptionText: "OmniRoute zero-cost free coding model",
},
{
id: "auto/coding:reliable",
displayName: "Auto: Coding Reliable (OmniRoute)",
descriptionText: "OmniRoute maximum uptime and reliability coding model",
},
{
id: "auto/reasoning:pro",
displayName: "Auto: Reasoning Pro (OmniRoute)",
descriptionText: "OmniRoute deep reasoning frontier model",
},
{
id: "auto/smart",
displayName: "Auto: Smart (OmniRoute)",
descriptionText: "OmniRoute highest intelligence general-purpose model",
},
{
id: "auto/claude-sonnet",
displayName: "Auto: Claude Sonnet (OmniRoute)",
descriptionText: "OmniRoute automated routing across Claude Sonnet providers",
},
{
id: "auto/claude-opus",
displayName: "Auto: Claude Opus (OmniRoute)",
descriptionText: "OmniRoute automated routing across Claude Opus providers",
},
{
id: "auto/gemini",
displayName: "Auto: Gemini (OmniRoute)",
descriptionText: "OmniRoute automated routing across Gemini providers",
},
{
id: "auto/llama",
displayName: "Auto: Llama (OmniRoute)",
descriptionText: "OmniRoute automated routing across Llama providers",
},
{
id: "auto/gemma",
displayName: "Auto: Gemma (OmniRoute)",
descriptionText: "OmniRoute automated routing across Gemma providers",
},
// Active, verified provider models
{
id: "groq/openai/gpt-oss-120b",
displayName: "Groq: GPT-OSS 120B (Ultra-Fast 0.02s)",
descriptionText: "Ultra-fast inference on Groq LPUs at sub-second speeds",
},
{
id: "groq/qwen/qwen3.8-27b",
displayName: "Groq: Qwen 3.8 27B",
descriptionText: "High-speed Qwen 3.8 27B model on Groq",
},
{
id: "mistral/codestral-latest",
displayName: "Mistral: Codestral Latest",
descriptionText: "Mistral flagship frontier code reasoning model",
},
{
id: "nvidia/nvidia/nemotron-3-super-120b-a12b",
displayName: "NVIDIA: Nemotron 3 Super 120B",
descriptionText: "Nemotron 3 Super 120B Deep Reasoning model on NVIDIA NIM",
},
{
id: "gemini/gemini-2.5-flash",
displayName: "Gemini: Gemini 2.5 Flash (AI Studio)",
descriptionText: "Google AI Studio direct Gemini 2.5 Flash route",
},
{
id: "gemini/gemini-2.5-pro",
displayName: "Gemini: Gemini 2.5 Pro (AI Studio)",
descriptionText: "Google AI Studio direct Gemini 2.5 Pro route",
},
];
const OMNIROUTE_CUSTOM_MODELS = new Set([
...OMNIROUTE_BUILTIN_GROUPS.map((g) => g.id),
...Object.keys(MODEL_ROUTING_MAP),
]);
function shouldInterceptToOmniRoute(model, url) {
if (!model) return false;
// Never intercept non-streaming unary RPCs (Antigravity expects raw JSON/Protobuf, not SSE)
const isStreaming =
url.includes("streamGenerateContent") ||
url.includes("StreamGenerateChat") ||
url.includes("alt=sse");
if (!isStreaming) return false;
// Never intercept native Google/Gemini models (used by Antigravity core, subagents, websearch, grounding)
if (model.startsWith("gemini-") || model.startsWith("models/gemini-")) {
return false;
}
// Never intercept native Google CloudCode PA hosted models
if (
model === "claude-sonnet-4-6" ||
model === "claude-opus-4-6" ||
model === "gpt-oss-120b-medium"
) {
return false;
}
// Intercept any OmniRoute auto group, provider model, or mapped alias
const clean = model.replace(/^models\//, "").trim();
if (
clean.startsWith("auto/") ||
clean.toLowerCase().includes("omniroute") ||
clean.includes("/") ||
OMNIROUTE_CUSTOM_MODELS.has(model) ||
OMNIROUTE_CUSTOM_MODELS.has(clean) ||
Boolean(MODEL_ROUTING_MAP[model]) ||
Boolean(MODEL_ROUTING_MAP[clean])
) {
return true;
}
return false;
}
const internalApp = http.createServer(async (req, res) => {
const host = (req.headers.host || "cloudcode-pa.googleapis.com").split(":")[0];
const url = req.url || "/";
// Collect request body
const chunks = [];
for await (const chunk of req) {
chunks.push(chunk);
}
const bodyBuffer = Buffer.concat(chunks);
let bodyJson = null;
if (bodyBuffer.length > 0) {
try {
bodyJson = JSON.parse(bodyBuffer.toString("utf-8"));
} catch {}
}
const model = extractModel(bodyJson, url);
const shouldIntercept = shouldInterceptToOmniRoute(model, url);
if (shouldIntercept) {
const resolvedModel = resolveTargetModel(model);
console.log(
`[Bridge] 🔀 INTERCEPTING -> OmniRoute: "${model || "default"}" => "${resolvedModel}" (${url})`
);
let outgoingBuffer = bodyBuffer;
if (bodyJson) {
const cloned = JSON.parse(JSON.stringify(bodyJson));
cloned.model = resolvedModel;
if (cloned.request && typeof cloned.request === "object") {
cloned.request.model = resolvedModel;
}
outgoingBuffer = Buffer.from(JSON.stringify(cloned), "utf-8");
}
// Forward to OmniRoute /v1/antigravity
try {
const forwardHeaders = {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(outgoingBuffer),
Authorization: `Bearer ${ROUTER_API_KEY}`,
"x-omniroute-source": "agent-bridge",
"x-omniroute-agent": "antigravity",
"x-omniroute-skip-usage": "true", // Skip usage tracking for default models
};
const upstreamReq = http.request(
ROUTER_URL,
{
method: "POST",
headers: forwardHeaders,
agent: httpAgent,
},
(upstreamRes) => {
res.writeHead(upstreamRes.statusCode || 200, upstreamRes.headers);
upstreamRes.pipe(res);
}
);
upstreamReq.setNoDelay(true);
upstreamReq.on("error", (err) => {
console.error(`[Bridge] ❌ Error forwarding to OmniRoute: ${err.message}`);
if (!res.headersSent) {
res.writeHead(502, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: { message: `OmniRoute bridge error: ${err.message}` } }));
}
});
upstreamReq.write(outgoingBuffer);
upstreamReq.end();
return;
} catch (err) {
console.error(`[Bridge] ❌ Failed to invoke OmniRoute: ${err.message}`);
}
}
// Otherwise: Passthrough directly to Google upstream
console.log(`[Bridge] ⏩ PASSTHROUGH -> Google: ${model || "non-model"} (${url})`);
const upstreamHeaders = { ...req.headers };
delete upstreamHeaders["host"]; // Let https.request set the correct Host
upstreamHeaders["host"] = host;
if (url.includes("fetchAvailableModels")) {
delete upstreamHeaders["accept-encoding"];
}
const googleReq = https.request(
{
hostname: host,
port: 443,
path: url,
method: req.method,
headers: upstreamHeaders,
agent: httpsAgent,
},
(googleRes) => {
if (url.includes("fetchAvailableModels")) {
const respChunks = [];
googleRes.on("data", (chunk) => respChunks.push(chunk));
googleRes.on("end", () => {
const respBuffer = Buffer.concat(respChunks);
let finalBuffer = respBuffer;
try {
const data = JSON.parse(respBuffer.toString("utf-8"));
if (data && data.models) {
// Inject OmniRoute built-in auto groups and models
const baseTemplate =
data.models["claude-sonnet-4-6"] ||
data.models["gpt-oss-120b-medium"] ||
Object.values(data.models)[0] ||
{};
const injectedIds = [];
for (const group of OMNIROUTE_BUILTIN_GROUPS) {
data.models[group.id] = {
...baseTemplate,
id: group.id,
name: group.id,
displayName: group.displayName,
descriptionText: group.descriptionText,
};
injectedIds.push(group.id);
}
// Prepend OmniRoute groups to agentModelSorts recommended group
if (
Array.isArray(data.agentModelSorts) &&
data.agentModelSorts[0]?.groups?.[0]?.modelIds
) {
const existing = data.agentModelSorts[0].groups[0].modelIds;
data.agentModelSorts[0].groups[0].modelIds = [
...injectedIds,
...existing.filter((id) => !injectedIds.includes(id)),
];
}
finalBuffer = Buffer.from(JSON.stringify(data), "utf-8");
console.log(
`[Bridge] 🌟 Injected custom models into fetchAvailableModels (${finalBuffer.length} bytes)`
);
}
} catch (err) {
console.error(`[Bridge] ⚠️ Error modifying fetchAvailableModels: ${err.message}`);
}
const headers = { ...googleRes.headers };
delete headers["content-length"];
delete headers["content-encoding"];
headers["content-length"] = String(finalBuffer.length);
res.writeHead(googleRes.statusCode || 200, headers);
res.end(finalBuffer);
});
return;
}
res.writeHead(googleRes.statusCode || 200, googleRes.headers);
googleRes.pipe(res);
}
);
googleReq.setNoDelay(true);
googleReq.on("error", (err) => {
console.error(`[Bridge] ❌ Google upstream error: ${err.message}`);
if (!res.headersSent) {
res.writeHead(502, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: { message: `Google upstream error: ${err.message}` } }));
}
});
if (bodyBuffer.length > 0) {
googleReq.write(bodyBuffer);
}
googleReq.end();
});
internalApp.keepAliveTimeout = 65000;
internalApp.headersTimeout = 66000;
// Proxy server listening on HTTP port
const proxyServer = http.createServer((req, res) => {
// Plain HTTP request (non-CONNECT)
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("OmniRoute Antigravity Bridge Proxy Active\n");
});
proxyServer.keepAliveTimeout = 65000;
proxyServer.headersTimeout = 66000;
proxyServer.on("connect", (req, clientSocket, head) => {
clientSocket.setNoDelay(true);
const [targetHost, targetPortStr] = (req.url || "").split(":");
const targetPort = parseInt(targetPortStr || "443", 10);
if (TARGET_HOSTS.has(targetHost)) {
// Target host: Terminate TLS locally and route via internalApp
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
const ssl = getSslOptions();
const tlsSocket = new tls.TLSSocket(clientSocket, {
isServer: true,
key: ssl.key,
cert: ssl.cert,
});
tlsSocket.setNoDelay(true);
tlsSocket.on("error", (err) => {
// Client closed or TLS error
clientSocket.destroy();
});
internalApp.emit("connection", tlsSocket);
} else {
// Non-target host: Transparent raw TCP tunnel
const upstreamSocket = net.connect(targetPort, targetHost, () => {
upstreamSocket.setNoDelay(true);
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
if (head && head.length > 0) {
upstreamSocket.write(head);
}
upstreamSocket.pipe(clientSocket);
clientSocket.pipe(upstreamSocket);
});
const cleanup = () => {
clientSocket.destroy();
upstreamSocket.destroy();
};
upstreamSocket.on("error", cleanup);
clientSocket.on("error", cleanup);
}
});
export {
resolveTargetModel,
MODEL_ROUTING_MAP,
shouldInterceptToOmniRoute,
extractModel,
OMNIROUTE_BUILTIN_GROUPS,
proxyServer,
internalApp,
};
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isMain) {
proxyServer.listen(PORT, "127.0.0.1", () => {
console.log(`🚀 OmniRoute Antigravity Bridge listening on 127.0.0.1:${PORT}`);
console.log(` Routing non-Gemini 3.8 model traffic -> ${ROUTER_URL}`);
console.log(` Preserving Gemini 3.8 native traffic -> Google`);
});
}

View File

@@ -79,8 +79,7 @@ export async function runChatCommand(promptArg, opts, cmd) {
const data = await response.json();
const text = extractText(data, opts.responsesApi);
// Commander stores `--no-history` as `history === false`, never as `noHistory`.
if (opts.history !== false && opts.noHistory !== true) {
if (!opts.noHistory) {
appendHistory({ prompt, model: opts.model, latencyMs, usage: data.usage, response: text });
}

View File

@@ -248,9 +248,7 @@ export function registerContexts(program) {
.option("--no-secrets", "Omit API keys from export")
.action(async (opts, cmd) => {
const cfg = loadContexts();
// Commander stores `--no-secrets` as `secrets === false`, never as `noSecrets`.
const redact = opts.secrets === false || opts.noSecrets === true;
const out = redact ? redactContextSecrets(cfg) : JSON.parse(JSON.stringify(cfg));
const out = opts.noSecrets ? redactContextSecrets(cfg) : JSON.parse(JSON.stringify(cfg));
const json = JSON.stringify(out, null, 2);
if (opts.out) {
const { writeFileSync } = await import("node:fs");

View File

@@ -10,7 +10,6 @@ import { getCliToken, CLI_TOKEN_HEADER } from "../utils/cliToken.mjs";
import { printHeading } from "../io.mjs";
import { t } from "../i18n.mjs";
import { readDatabaseHealth, readEncryptedCredentialSamples } from "../sqlite.mjs";
import { getCrashLogPath } from "../runtime/processSupervisor.mjs";
const STATIC_SALT = "omniroute-field-encryption-v1";
const KEY_LENGTH = 32;
@@ -381,33 +380,6 @@ function checkMemory() {
});
}
// #13538: surfaces the supervisor's give-up crash record (persisted by
// ServerSupervisor.persistCrashLog(), bin/cli/runtime/processSupervisor.mjs)
// so a user whose `--tray` worker died silently (detached, stdio:"ignore")
// has something concrete `doctor` can point at without needing `--log`.
function checkCrashLog() {
const crashLogPath = getCrashLogPath();
if (!fs.existsSync(crashLogPath)) {
return ok("Crash log", "No supervisor crash record found", { crashLogPath });
}
try {
const stat = fs.statSync(crashLogPath);
const contents = fs.readFileSync(crashLogPath, "utf8");
const lastEntry = contents.split("\n").filter(Boolean).slice(-6).join("\n");
return warn(
"Crash log",
`Supervisor recorded a give-up crash at ${crashLogPath} (last modified ${stat.mtime.toISOString()})`,
{ crashLogPath, modifiedAt: stat.mtime.toISOString(), tail: lastEntry }
);
} catch (error) {
return warn("Crash log", `Crash record exists at ${crashLogPath} but could not be read`, {
crashLogPath,
error: error instanceof Error ? error.message : String(error),
});
}
}
async function fetchWithTimeout(url, options = {}) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
@@ -607,7 +579,6 @@ export async function collectDoctorChecks(context = {}, options = {}) {
checks.push(await checkNodeRuntime(rootDir));
checks.push(await checkNativeBinary(rootDir));
checks.push(checkMemory());
checks.push(checkCrashLog());
if (!options.skipLiveness) {
checks.push(await checkServerLiveness(options));

View File

@@ -352,24 +352,10 @@ export async function runKeysRegenerateCommand(id, opts = {}) {
return 1;
}
try {
const encodedId = encodeURIComponent(id);
let res = await apiFetch(`/api/v1/registered-keys/${encodedId}/regenerate`, {
const res = await apiFetch(`/api/v1/registered-keys/${encodeURIComponent(id)}/regenerate`, {
method: "POST",
retry: false,
acceptNotOk: true,
});
// `keys` predates the split between registered keys and the dashboard's
// ordinary API keys. IDs shown by `keys list`/the dashboard belong to
// `/api/keys`, while deployment/registered-key IDs belong to
// `/api/v1/registered-keys`. Try the ordinary-key route when the ID is not
// present in the registered-key store so the command works with either ID.
if (isRouteUnavailableStatus(res.status)) {
res = await apiFetch(`/api/keys/${encodedId}/regenerate`, {
method: "POST",
retry: false,
acceptNotOk: true,
});
}
if (!res.ok) {
console.error(t("common.error", { message: `HTTP ${res.status}` }));
return 1;
@@ -424,17 +410,9 @@ export async function runKeysRevealCommand(id, opts = {}) {
return 1;
}
try {
const encodedId = encodeURIComponent(id);
let res = await apiFetch(`/api/v1/registered-keys/${encodedId}/reveal`, {
const res = await apiFetch(`/api/v1/registered-keys/${encodeURIComponent(id)}/reveal`, {
retry: false,
acceptNotOk: true,
});
if (isRouteUnavailableStatus(res.status)) {
res = await apiFetch(`/api/keys/${encodedId}/reveal`, {
retry: false,
acceptNotOk: true,
});
}
if (!res.ok) {
console.error(t("common.error", { message: `HTTP ${res.status}` }));
return 1;

View File

@@ -9,8 +9,6 @@ function truncate(v, len = 60) {
return s.length > len ? s.slice(0, len - 1) + "…" : s;
}
const VALID_MCP_TRANSPORTS = ["stdio", "sse", "streamable-http"];
const mcpToolSchema = [
{ key: "name", header: "Tool", width: 36 },
{
@@ -45,25 +43,6 @@ export function registerMcp(program) {
if (exitCode !== 0) process.exit(exitCode);
});
mcp
.command("enable")
.description(t("mcp.enable.description"))
.option("--transport <transport>", t("mcp.enable.transport"))
.action(async (opts, cmd) => {
const globalOpts = cmd.parent.optsWithGlobals();
const exitCode = await runMcpEnableCommand({ ...opts, output: globalOpts.output });
if (exitCode !== 0) process.exit(exitCode);
});
mcp
.command("disable")
.description(t("mcp.disable.description"))
.action(async (opts, cmd) => {
const globalOpts = cmd.parent.optsWithGlobals();
const exitCode = await runMcpDisableCommand({ ...opts, output: globalOpts.output });
if (exitCode !== 0) process.exit(exitCode);
});
// 5.1 — mcp call + mcp scopes
mcp
.command("call <tool> [argsJson]")
@@ -82,15 +61,10 @@ export function registerMcp(program) {
? JSON.parse(argsPositional)
: {};
const exitCode = await runMcpCallCommand(
tool,
args,
{
...opts,
stream: opts.stream,
},
globalOpts
);
const exitCode = await runMcpCallCommand(tool, args, {
...opts,
stream: opts.stream,
}, globalOpts);
if (exitCode !== 0) process.exit(exitCode);
});
@@ -153,9 +127,7 @@ async function mcpJsonRpcCall(tool, args, { stream = false, globalOpts = {} } =
if (!initRes.ok) {
const text = await initRes.text().catch(() => "");
process.stderr.write(
`MCP initialize failed: HTTP ${initRes.status}${text ? `${text}` : ""}\n`
);
process.stderr.write(`MCP initialize failed: HTTP ${initRes.status}${text ? `${text}` : ""}\n`);
return 1;
}
@@ -255,7 +227,6 @@ export async function runMcpStatusCommand(opts = {}) {
});
if (!res.ok) {
console.log(t("mcp.stopped"));
console.log(t("mcp.stoppedHint"));
return 0;
}
@@ -269,9 +240,6 @@ export async function runMcpStatusCommand(opts = {}) {
const transport = status.transport || "stdio";
const online = status.online ?? status.running;
console.log(online ? t("mcp.running", { transport }) : t("mcp.stopped"));
if (!online && status.enabled === false) {
console.log(t("mcp.stoppedHint"));
}
if (status.toolsCount !== undefined) console.log(` Tools: ${status.toolsCount}`);
if (status.scopes?.length) {
console.log(" Scopes:");
@@ -302,76 +270,10 @@ export async function runMcpRestartCommand(opts = {}) {
console.log(t("mcp.restarted"));
return 0;
}
const body = await res.json().catch(() => null);
const message = body?.error || `HTTP ${res.status}`;
console.error(t("common.error", { message }));
console.error(t("common.error", { message: `HTTP ${res.status}` }));
return 1;
} catch (err) {
console.error(t("common.error", { message: err instanceof Error ? err.message : String(err) }));
return 1;
}
}
export async function runMcpEnableCommand(opts = {}) {
const serverUp = await isServerUp();
if (!serverUp) {
console.error(t("common.serverOffline"));
return 1;
}
if (opts.transport && !VALID_MCP_TRANSPORTS.includes(opts.transport)) {
console.error(
t("common.error", {
message: `Invalid transport '${opts.transport}'. Valid: ${VALID_MCP_TRANSPORTS.join(", ")}`,
})
);
return 1;
}
try {
const body = { mcpEnabled: true };
if (opts.transport) body.mcpTransport = opts.transport;
const res = await apiFetch("/api/settings", {
method: "PATCH",
body,
retry: false,
acceptNotOk: true,
});
if (!res.ok) {
console.error(t("common.error", { message: `HTTP ${res.status}` }));
return 1;
}
console.log(t("mcp.enabled"));
return 0;
} catch (err) {
console.error(t("common.error", { message: err instanceof Error ? err.message : String(err) }));
return 1;
}
}
export async function runMcpDisableCommand(opts = {}) {
const serverUp = await isServerUp();
if (!serverUp) {
console.error(t("common.serverOffline"));
return 1;
}
try {
const res = await apiFetch("/api/settings", {
method: "PATCH",
body: { mcpEnabled: false },
retry: false,
acceptNotOk: true,
});
if (!res.ok) {
console.error(t("common.error", { message: `HTTP ${res.status}` }));
return 1;
}
console.log(t("mcp.disabled"));
return 0;
} catch (err) {
console.error(t("common.error", { message: err instanceof Error ? err.message : String(err) }));
return 1;
}
}

View File

@@ -23,13 +23,8 @@ const PROVIDERS_WITH_OAUTH = [
// the device-flow request to /api/providers/command-code/auth/start, which is
// gated by requireManagementAuth and returned 401 for a fresh CLI context
// (issue #9474). Map the alias to the real backend key instead.
//
// `copilot` has the same mismatch (#14298): the GitHub Copilot device flow is
// registered under the backend key `github`, so posting to
// /api/oauth/copilot/device-code failed for an unknown provider.
const BACKEND_OAUTH_KEY = {
"claude-code": "claude",
copilot: "github",
};
function resolveBackendKey(id) {
@@ -75,7 +70,7 @@ function printLoopbackRedirectWarning(providerId, redirectUri) {
process.stdout.write(
`Note: the authorize URL below advertises ${redirectUri}, but this CLI does not\n` +
"listen on that port. Right after you approve, the browser is expected to\n" +
'show a connection error (e.g. "This site can\'t be reached" / \n' +
"show a connection error (e.g. \"This site can't be reached\" / \n" +
"ERR_CONNECTION_REFUSED) — that is normal, not a failure. Copy the full URL\n" +
"from the address bar anyway and paste it below.\n"
);
@@ -297,51 +292,27 @@ async function runDeviceFlow(def, opts) {
if (opts.browser !== false && verificationUri) await openBrowser(verificationUri);
process.stderr.write("Waiting for device authorization...\n");
// Poll the real device-flow route: POST /api/oauth/{key}/poll with the device
// code (#14298). The previous implementation polled
// GET /api/providers/{key}/auth/status?state=… and then POST …/auth/apply,
// but neither route exists on the server, and the device-code response has no
// `state` field at all — so the CLI looped until its timeout even after the
// user authorized. /api/oauth/{key}/poll is the same route the dashboard
// polls (src/shared/components/OAuthModal.tsx::pollDeviceCodeOnce) and it
// persists the connection server-side on success, so no separate apply step
// is needed.
const deviceCode = start.deviceCode ?? start.device_code ?? "";
if (!deviceCode) {
process.stderr.write("Server did not return a device code; cannot poll for authorization.\n");
process.exit(1);
}
const codeVerifier = start.codeVerifier ?? undefined;
const deadline = Date.now() + (opts.timeout ?? 300000);
let intervalMs = (start.intervalMs ?? start.interval ?? 5) * 1000;
const intervalMs = (start.intervalMs ?? start.interval ?? 5) * 1000;
while (Date.now() < deadline) {
await sleep(intervalMs);
const pollRes = await apiFetch(`/api/oauth/${providerKey}/poll`, {
...targetApiOptions(opts),
method: "POST",
body: { deviceCode, ...(codeVerifier ? { codeVerifier } : {}) },
});
if (!pollRes.ok) continue;
let poll;
try {
poll = await pollRes.json();
} catch {
continue;
}
if (poll.success) {
const conn = poll.connection ?? {};
process.stdout.write(
`Authorized: ${conn.email ?? conn.displayName ?? conn.id ?? "connected"}\n`
);
const statusRes = await apiFetch(
`/api/providers/${providerKey}/auth/status?state=${encodeURIComponent(start.state ?? "")}`,
targetApiOptions(opts)
);
if (!statusRes.ok) continue;
const status = await statusRes.json();
if (status.status === "complete" || status.status === "authorized") {
await apiFetch(`/api/providers/${providerKey}/auth/apply`, {
...targetApiOptions(opts),
method: "POST",
body: { state: start.state },
});
process.stdout.write(`Authorized: ${status.account ?? status.email ?? "connected"}\n`);
return;
}
if (poll.error === "slow_down") {
// OAuth device-flow spec: back off by 5s on slow_down.
intervalMs += 5000;
continue;
}
if (poll.error && !poll.pending) {
process.stderr.write(`Device auth failed: ${poll.errorDescription ?? poll.error}\n`);
if (status.status === "error") {
process.stderr.write(`Device auth failed: ${status.error}\n`);
process.exit(1);
}
}

View File

@@ -6,8 +6,7 @@ export function registerRestart(program) {
program
.command("restart")
.description(t("restart.description"))
// No Commander default: runServe() falls back to PORT, then 20128 (#7049).
.option("--port <port>", t("serve.port"))
.option("--port <port>", t("serve.port"), "20128")
.action(async (opts) => {
const exitCode = await runRestartCommand(opts);
if (exitCode !== 0) process.exit(exitCode);

View File

@@ -4,13 +4,7 @@ import { join, dirname } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { platform, totalmem } from "node:os";
import { t } from "../i18n.mjs";
import {
writePidFile,
cleanupPidFile,
waitForServer,
findListeningPids,
resolveReadyTimeoutMs,
} from "../utils/pid.mjs";
import { writePidFile, cleanupPidFile, waitForServer, resolveReadyTimeoutMs } from "../utils/pid.mjs";
import {
ServerSupervisor,
detectMitmCrash,
@@ -241,16 +235,6 @@ export async function runServe(opts = {}) {
process.exit(1);
}
// Refuse to start a second instance on a port something else already owns,
// BEFORE any pid file is written or any child is spawned. Otherwise the
// doomed child's EADDRINUSE arrives only after this process has rewritten
// the pid files of the healthy instance that actually owns the port.
const busyPids = await findListeningPids(dashboardPort);
if (busyPids.length > 0) {
reportPortInUse(dashboardPort, busyPids);
process.exit(1);
}
console.log(` \x1b[2m⏳ Starting server...\x1b[0m\n`);
// #5172/#5160/#5152: default the V8 heap to ~35% of physical RAM (clamped
@@ -297,8 +281,7 @@ export async function runServe(opts = {}) {
return runDaemon(serverJs, env, memoryLimit, dashboardPort, apiPort);
}
// Commander stores `--no-recovery` as `recovery === false`, never as `noRecovery`.
if (opts.recovery === false || opts.noRecovery === true) {
if (opts.noRecovery) {
return runWithoutRecovery(
serverJs,
env,
@@ -321,29 +304,10 @@ export async function runServe(opts = {}) {
opts.maxRestarts ?? 2,
startedAt,
useTray,
{
trayReadyPort: opts.trayReadyPort,
trayReadyToken: opts.trayReadyToken,
readyTimeoutMs: resolveReadyTimeoutMs({ timeoutMs: opts.readyTimeout }),
}
{ trayReadyPort: opts.trayReadyPort, trayReadyToken: opts.trayReadyToken }
);
}
/**
* Explain a port conflict in terms the operator can act on: who owns the port,
* and the two ways out. Exported for unit tests.
*/
export function reportPortInUse(port, pids = []) {
const owner = pids.length === 1 ? `PID ${pids[0]}` : `PIDs ${pids.join(", ")}`;
console.error(`\n\x1b[31m✖ Port ${port} is already in use by ${owner}.\x1b[0m`);
console.error(
` Another OmniRoute is most likely already serving there, so open` +
` ${urlScheme}://localhost:${port} before starting a second one.`
);
console.error(` To replace it: \x1b[36momniroute stop\x1b[0m, then start again`);
console.error(` To run alongside: \x1b[36momniroute serve --port <other-port>\x1b[0m\n`);
}
function runDaemon(serverJs, env, memoryLimit, dashboardPort, apiPort) {
// #5238: skip the explicit CLI --max-old-space-size when the user pinned the
// heap via NODE_OPTIONS (a CLI arg would shadow/override their value).
@@ -454,7 +418,7 @@ async function runWithSupervisor(
maxRestarts,
startedAt,
useTray = false,
{ trayReadyPort, trayReadyToken, readyTimeoutMs = resolveReadyTimeoutMs() } = {}
{ trayReadyPort, trayReadyToken } = {}
) {
if (showLog) process.env.OMNIROUTE_SHOW_LOG = "1";
writePidFile("supervisor", process.pid);
@@ -493,12 +457,8 @@ async function runWithSupervisor(
});
if (!showLog) {
let lastProbeOutcome = null;
waitForServer(dashboardPort, readyTimeoutMs, {
onOutcome: (outcome) => {
lastProbeOutcome = outcome;
},
}).then(async (up) => {
const readyTimeoutMs = resolveReadyTimeoutMs({ timeoutMs: opts.readyTimeout });
waitForServer(dashboardPort, readyTimeoutMs).then(async (up) => {
if (up) {
if (useTray) {
const trayReady = await maybeStartTray(dashboardPort, apiPort, supervisor);
@@ -522,7 +482,7 @@ async function runWithSupervisor(
}
onReady(dashboardPort, apiPort, noOpen, startedAt);
} else {
reportReadinessTimeout(dashboardPort, supervisor, lastProbeOutcome);
reportReadinessTimeout(dashboardPort, supervisor);
}
});
}
@@ -534,28 +494,13 @@ async function runWithSupervisor(
// stuck (issue reports show the server sometimes actually comes up later, or is
// reachable directly while the CLI still looks hung). Surface a clear diagnostic
// plus whatever stdout/stderr the child buffered instead of going silent.
export function reportReadinessTimeout(dashboardPort, supervisor, lastProbeOutcome = null) {
export function reportReadinessTimeout(dashboardPort, supervisor) {
const readyTimeoutMs = resolveReadyTimeoutMs();
const seconds = Math.round(readyTimeoutMs / 1000);
console.error(
`\n\x1b[33m⚠ Server did not respond within ${seconds}s.\x1b[0m It may still be starting, or may` +
` have failed silently.`
);
// The last probe classification separates a real boot failure (nothing ever
// bound the port, so the buffered output below is the reason) from a server
// that IS listening and merely did not answer the health route in time:
// very likely usable already, with only the readiness signal timed out.
if (lastProbeOutcome === "hanging" || lastProbeOutcome === "fast-reject") {
console.error(
` Port ${dashboardPort} IS accepting connections, so the server is probably up and` +
` still warming up. Check the dashboard before restarting it.`
);
} else if (lastProbeOutcome === "not-listening") {
console.error(
` Nothing is listening on port ${dashboardPort}, so the server never bound it and the` +
` output below is the reason.`
);
}
console.error(
` Tip: set OMNIROUTE_READY_TIMEOUT_MS=${readyTimeoutMs * 2} or --ready-timeout ${readyTimeoutMs * 2} for slower cold starts.`
);

View File

@@ -187,9 +187,7 @@ export async function runUpdateCommand(opts = {}) {
}
if (dryRun) {
console.log(
"\n [DRY RUN] Would run: npm install -g omniroute@latest --include=optional --legacy-peer-deps"
);
console.log("\n [DRY RUN] Would run: npm install -g omniroute@latest --include=optional");
if (!skipBackup) console.log(" [DRY RUN] Would create backup in ~/.omniroute/backups/");
return 0;
}
@@ -223,9 +221,7 @@ export async function runUpdateCommand(opts = {}) {
const { execSync } = await import("child_process");
// --include=optional keeps the optionalDependencies (better-sqlite3, keytar,
// tls-client, llmlingua SLM stack) on update so an omit=optional config can't drop them.
execSync("npm install -g omniroute@latest --include=optional --legacy-peer-deps", {
stdio: "inherit",
});
execSync("npm install -g omniroute@latest --include=optional", { stdio: "inherit" });
// Trust-but-verify: `npm install -g` exits 0 even when a shadowing local install
// (e.g. ~/node_modules/omniroute ahead of the global prefix on PATH) means the
// binary the user actually runs was not touched. Re-read the running binary's

View File

@@ -30,7 +30,7 @@
"opencode": "ከOpenCode ጋር የተካተተውን @omniroute/opencode-plugin ጫን እና አዋቅር"
},
"doctor": {
"title": "OmniRoute ዶክተር",
"title": "OmniRoute Doctor",
"dbOk": "የውሂብ ጎታ፦ ደህና ({path})",
"dbMissing": "የውሂብ ጎታ፦ አልተጀመረም — `omniroute setup` ያስኪዱ",
"portOk": "ወደብ {port}፦ ይገኛል",
@@ -256,7 +256,6 @@
"max_restarts": "ከመተው በፊት በ30s ውስጥ የሚፈቀደው ከፍተኛ የብልሽት ዳግም መጀመር ብዛት (ነባሪ፦ 2)",
"tray": "በስርዓት ትሪ ውስጥ አስጀምር (ለዴስክቶፕ ብቻ፣ በፈቃድ)",
"no_tray": "የስርዓት ትሪ አዶን አሰናክል",
"ready_timeout": "የዝግጁነት ምርመራ ጊዜ ማብቂያ በሚሊሰከንድ (እንዲሁም OMNIROUTE_READY_TIMEOUT_MS፣ ነባሪ 60000)",
"tls_cert": "HTTPSን ለማቅረብ የTLS ሰርተፍኬት (PEM) ዱካ (OMNIROUTE_TLS_CERTም ጭምር)",
"tls_key": "HTTPSን ለማቅረብ የTLS የግል ቁልፍ (PEM) ዱካ (OMNIROUTE_TLS_KEYም ጭምር)"
},
@@ -348,16 +347,6 @@
"running": "MCP ሰርቨር እየሰራ ነው ({transport})",
"stopped": "MCP ሰርቨር ቆሟል።",
"restarted": "MCP ሰርቨር እንደገና ተጀምሯል።",
"stoppedHint": "ለማብራት `omniroute mcp enable`ን ያሂዱ።",
"enabled": "MCP አገልጋይ ነቅቷል።",
"disabled": "MCP አገልጋይ ተሰናክሏል።",
"enable": {
"description": "MCP አገልጋዩን አንቃ",
"transport": "ጥቅም ላይ የሚውል ማጓጓዣ፦ stdio|sse|streamable-http"
},
"disable": {
"description": "MCP አገልጋዩን አሰናክል"
},
"call": {
"description": "የMCP መሣሪያን በቀጥታ ይጥሩ",
"args": "የJSON ነጋሪ እሴቶች ኦብጀክት (በቦታው)",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Μέγιστος αριθμός επανεκκινήσεων λόγω σφάλματος εντός 30 δευτερολέπτων πριν την οριστική διακοπή (προεπιλογή: 2)",
"tray": "Εκκίνηση στην περιοχή ειδοποιήσεων (μόνο για επιτραπέζιους υπολογιστές, προαιρετικό)",
"no_tray": "Απενεργοποίηση εικονιδίου περιοχής ειδοποιήσεων",
"ready_timeout": "Χρονικό όριο λήξης του ελέγχου ετοιμότητας σε ms (επίσης OMNIROUTE_READY_TIMEOUT_MS, προεπιλογή 60000)",
"tls_cert": "Διαδρομή προς πιστοποιητικό TLS (PEM) για εξυπηρέτηση HTTPS (επίσης OMNIROUTE_TLS_CERT)",
"tls_key": "Διαδρομή προς το ιδιωτικό κλειδί TLS (PEM) για εξυπηρέτηση HTTPS (επίσης OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "Ο διακομιστής MCP εκτελείται ({transport})",
"stopped": "Ο διακομιστής MCP διακόπηκε.",
"restarted": "Ο διακομιστής MCP επανεκκινήθηκε.",
"stoppedHint": "Εκτελέστε `omniroute mcp enable` για να το ενεργοποιήσετε.",
"enabled": "Ο διακομιστής MCP ενεργοποιήθηκε.",
"disabled": "Ο διακομιστής MCP απενεργοποιήθηκε.",
"enable": {
"description": "Ενεργοποίηση του διακομιστή MCP",
"transport": "Μέθοδος μεταφοράς προς χρήση: stdio|sse|streamable-http"
},
"disable": {
"description": "Απενεργοποίηση του διακομιστή MCP"
},
"call": {
"description": "Άμεση κλήση εργαλείου MCP",
"args": "Αντικείμενο ορισμάτων JSON (ενσωματωμένο)",
@@ -1104,7 +1093,7 @@
}
},
"combo": {
"title": "Συνδυασμοί",
"title": "Combos",
"switched": "Ενεργό combo: {name}",
"created": "Δημιουργήθηκε combo: {name}",
"deleted": "Διαγράφηκε combo: {name}",

View File

@@ -348,16 +348,6 @@
"running": "MCP server running ({transport})",
"stopped": "MCP server stopped.",
"restarted": "MCP server restarted.",
"stoppedHint": "Run `omniroute mcp enable` to turn it on.",
"enabled": "MCP server enabled.",
"disabled": "MCP server disabled.",
"enable": {
"description": "Enable the MCP server",
"transport": "Transport to use: stdio|sse|streamable-http"
},
"disable": {
"description": "Disable the MCP server"
},
"call": {
"description": "Invoke an MCP tool directly",
"args": "JSON arguments object (inline)",

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@
"opencode": "Installi ja seadista OpenCode'i jaoks kaasas olev @omniroute/opencode-plugin"
},
"doctor": {
"title": "OmniRoute'i diagnostika",
"title": "OmniRoute Doctor",
"dbOk": "Andmebaas: korras ({path})",
"dbMissing": "Andmebaas: pole lähtestatud — käivita `omniroute setup`",
"portOk": "Port {port}: saadaval",
@@ -256,7 +256,6 @@
"max_restarts": "Maksimaalne krahhijärgsete taaskäivituste arv 30 sekundi jooksul enne alla andmist (vaikimisi: 2)",
"tray": "Käivita süsteemisalves (ainult töölaual, valikuline)",
"no_tray": "Keela süsteemisalve ikoon",
"ready_timeout": "Valmisolekukontrolli ajalõpp millisekundites (ka OMNIROUTE_READY_TIMEOUT_MS, vaikimisi 60000)",
"tls_cert": "TLS-sertifikaadi (PEM) tee HTTPS-i teenindamiseks (ka OMNIROUTE_TLS_CERT)",
"tls_key": "TLS privaatvõtme (PEM) tee HTTPS-i teenindamiseks (ka OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "MCP server töötab ({transport})",
"stopped": "MCP server peatatud.",
"restarted": "MCP server taaskäivitatud.",
"stoppedHint": "Selle sisselülitamiseks käivitage `omniroute mcp enable`.",
"enabled": "MCP-server on lubatud.",
"disabled": "MCP-server on keelatud.",
"enable": {
"description": "Luba MCP-server",
"transport": "Kasutatav transport: stdio|sse|streamable-http"
},
"disable": {
"description": "Keela MCP-server"
},
"call": {
"description": "Käivita MCP tööriist otse",
"args": "JSON argumentide objekt (otseselt sisestatud)",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Uaslíon atosuithe titim laistigh de 30 sula bhfágann tú suas (réamhshocrú: 2)",
"tray": "Tosaigh i mbosca córas (leicsitheoir amháin, roghnach)",
"no_tray": "Díchumasaigh deilbhín bosca córas",
"ready_timeout": "Teorainn ama an tseiceála ullmhachta i ms (OMNIROUTE_READY_TIMEOUT_MS freisin, réamhshocrú 60000)",
"tls_cert": "Cosán go dtí deimhniú TLS (PEM) chun HTTPS a sheirbhísiú (freisin OMNIROUTE_TLS_CERT)",
"tls_key": "Cosán go dtí eochair phríobháideach TLS (PEM) chun HTTPS a sheirbhísiú (freisin OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "Tá freastalaí MCP ag rith ({transport})",
"stopped": "Tá freastalaí MCP stoptha.",
"restarted": "Tá freastalaí MCP atosaíte.",
"stoppedHint": "Rith `omniroute mcp enable` chun é a chur ar siúl.",
"enabled": "Freastalaí MCP cumasaithe.",
"disabled": "Freastalaí MCP díchumasaithe.",
"enable": {
"description": "Cumasaigh an freastalaí MCP",
"transport": "Iompar le húsáid: stdio|sse|streamable-http"
},
"disable": {
"description": "Díchumasaigh an freastalaí MCP"
},
"call": {
"description": "Glaoigh ar uirlis MCP go díreach",
"args": "Réimse argóintí JSON (inlíne)",

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Matsakaicin sake farawa bayan rushewa cikin daƙiƙa 30 kafin a haƙura (tsoho: 2)",
"tray": "Fara a tiren tsarin (na kwamfutar tebur kawai, sai an zaɓa)",
"no_tray": "Kashe gunkin tiren tsarin",
"ready_timeout": "Lokacin ƙarewar gwajin shiri a ms (har ila yau OMNIROUTE_READY_TIMEOUT_MS, tsoho 60000)",
"tls_cert": "Hanyar zuwa takardar shaidar TLS (PEM) don samar da HTTPS (haka kuma OMNIROUTE_TLS_CERT)",
"tls_key": "Hanyar zuwa maɓallin sirri na TLS (PEM) don samar da HTTPS (haka kuma OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "Sabar MCP tana aiki ({transport})",
"stopped": "An dakatar da sabar MCP.",
"restarted": "An sake kunna sabar MCP.",
"stoppedHint": "Gudanar da `omniroute mcp enable` don kunna shi.",
"enabled": "An kunna sabar MCP.",
"disabled": "An kashe sabar MCP.",
"enable": {
"description": "Kunna sabar MCP",
"transport": "Hanyar jigilar da za a yi amfani da ita: stdio|sse|streamable-http"
},
"disable": {
"description": "Kashe sabar MCP"
},
"call": {
"description": "Kira kayan aikin MCP kai tsaye",
"args": "Abun hujjojin JSON (a cikin layi)",
@@ -486,7 +475,7 @@
}
},
"tunnel": {
"title": "Ramuka",
"title": "Tunnels",
"listDescription": "Jera tunnels masu aiki",
"createDescription": "Ƙirƙiri tunnel",
"created": "An ƙirƙiri tunnel: {url}",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Maksimalni broj ponovnih pokretanja nakon pada unutar 30 s prije odustajanja (zadano: 2)",
"tray": "Pokretanje u programskoj traci (samo za stolna računala, po izboru)",
"no_tray": "Onemogući ikonu programske trake",
"ready_timeout": "Vremensko ograničenje provjere spremnosti u ms (također OMNIROUTE_READY_TIMEOUT_MS, zadano 60000)",
"tls_cert": "Putanja do TLS certifikata (PEM) za posluživanje HTTPS-a (također OMNIROUTE_TLS_CERT)",
"tls_key": "Putanja do TLS privatnog ključa (PEM) za posluživanje HTTPS-a (također OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "MCP poslužitelj je pokrenut ({transport})",
"stopped": "MCP poslužitelj je zaustavljen.",
"restarted": "MCP poslužitelj je ponovo pokrenut.",
"stoppedHint": "Pokrenite `omniroute mcp enable` da biste ga uključili.",
"enabled": "MCP poslužitelj omogućen.",
"disabled": "MCP poslužitelj onemogućen.",
"enable": {
"description": "Omogući MCP poslužitelj",
"transport": "Prijenos koji će se koristiti: stdio|sse|streamable-http"
},
"disable": {
"description": "Onemogući MCP poslužitelj"
},
"call": {
"description": "Izravno pozovi MCP alat",
"args": "JSON objekt argumenata (unutarnji)",

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Վթարից հետո վերագործարկումների առավելագույն քանակը 30 վրկ-ի ընթացքում՝ մինչև փորձերը դադարեցնելը (լռելյայն՝ 2)",
"tray": "Գործարկել համակարգային սկուտեղում (միայն աշխատասեղանի տարբերակում, ըստ ցանկության)",
"no_tray": "Անջատել համակարգային սկուտեղի պատկերակը",
"ready_timeout": "Պատրաստության ստուգման սպասաժամը՝ մվ-ով (նաև OMNIROUTE_READY_TIMEOUT_MS, լռելյայն՝ 60000)",
"tls_cert": "HTTPS սպասարկելու համար TLS վկայագրի (PEM) ուղին (նաև՝ OMNIROUTE_TLS_CERT)",
"tls_key": "HTTPS սպասարկելու համար TLS գաղտնի բանալու (PEM) ուղին (նաև՝ OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "MCP սերվերը գործարկված է ({transport})",
"stopped": "MCP սերվերը կանգնեցվել է։",
"restarted": "MCP սերվերը վերագործարկվել է։",
"stoppedHint": "Այն միացնելու համար գործարկեք `omniroute mcp enable` հրամանը։",
"enabled": "MCP սերվերը միացված է։",
"disabled": "MCP սերվերն անջատված է։",
"enable": {
"description": "Միացնել MCP սերվերը",
"transport": "Օգտագործվող փոխադրման եղանակը՝ stdio|sse|streamable-http"
},
"disable": {
"description": "Անջատել MCP սերվերը"
},
"call": {
"description": "Անմիջապես կանչել MCP գործիք",
"args": "JSON արգումենտների օբյեկտ (ներտողային)",

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Ọnụọgụ mmalitegharị kachasị elu mgbe ọ dara n'ime 30s tupu ịkwụsị mbọ (ndabara: 2)",
"tray": "Malite na tray sistemụ (naanị na desktọpụ, ma ọ bụrụ na ahọrọ ya)",
"no_tray": "Gbanyụọ akara ngosi tray sistemụ",
"ready_timeout": "Oge njedebe nyocha ịdị njikere na ms (yana OMNIROUTE_READY_TIMEOUT_MS, ndabara bụ 60000)",
"tls_cert": "Ụzọ gaa na asambodo TLS (PEM) iji nye HTTPS (nakwa OMNIROUTE_TLS_CERT)",
"tls_key": "Ụzọ gaa na igodo nzuzo TLS (PEM) iji nye HTTPS (nakwa OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "Sava MCP na-arụ ọrụ ({transport})",
"stopped": "Sava MCP akwụsịla.",
"restarted": "Amalitegharịrị sava MCP.",
"stoppedHint": "Gbaa `omniroute mcp enable` iji gbanye ya.",
"enabled": "Agbanyela sava MCP.",
"disabled": "Agbanyụọla sava MCP.",
"enable": {
"description": "Gbanye sava MCP",
"transport": "Usoro mbufe a ga-eji: stdio|sse|streamable-http"
},
"disable": {
"description": "Gbanyụọ sava MCP"
},
"call": {
"description": "Kpọọ ngwa MCP ozugbo",
"args": "Ihe arụmụka JSON (n'ime ahịrị)",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "30 წამში ავარიული გათიშვის შემდეგ ხელახალი გაშვებების მაქსიმალური რაოდენობა დანებებამდე (ნაგულისხმევი: 2)",
"tray": "სისტემურ უჯრაში გაშვება (მხოლოდ დესკტოპისთვის, სურვილისამებრ)",
"no_tray": "სისტემური უჯრის ხატულის გამორთვა",
"ready_timeout": "მზადყოფნის შემოწმების მოლოდინის დრო მილიწამებში (ასევე OMNIROUTE_READY_TIMEOUT_MS, ნაგულისხმევად 60000)",
"tls_cert": "HTTPS-ისთვის TLS სერტიფიკატის (PEM) გზა (ასევე OMNIROUTE_TLS_CERT)",
"tls_key": "HTTPS-ისთვის TLS პირადი გასაღების (PEM) გზა (ასევე OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "MCP სერვერი მუშაობს ({transport})",
"stopped": "MCP სერვერი გაჩერდა.",
"restarted": "MCP სერვერი გადაიტვირთა.",
"stoppedHint": "მის ჩასართავად გაუშვით `omniroute mcp enable`.",
"enabled": "MCP სერვერი ჩართულია.",
"disabled": "MCP სერვერი გამორთულია.",
"enable": {
"description": "MCP სერვერის ჩართვა",
"transport": "გამოსაყენებელი ტრანსპორტი: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP სერვერის გამორთვა"
},
"call": {
"description": "MCP ხელსაწყოს პირდაპირ გამოძახება",
"args": "JSON არგუმენტების ობიექტი (ჩაშენებული)",

View File

@@ -86,7 +86,7 @@
}
},
"keys": {
"title": "សោ API",
"title": "API Keys",
"addDescription": "បន្ថែម ឬធ្វើបច្ចុប្បន្នភាព API key សម្រាប់អ្នកផ្តល់សេវា",
"listDescription": "រាយ API keys ដែលបានកំណត់រចនាសម្ព័ន្ធទាំងអស់",
"removeDescription": "លុប API key សម្រាប់អ្នកផ្តល់សេវា",
@@ -226,7 +226,7 @@
"description": "ផ្ញើ chat prompt មួយលើកទៅ OmniRoute",
"file": "អាន prompt ពីឯកសារ",
"stdin": "អាន prompt ពី stdin",
"system": "ប្រូមប្រព័ន្ធ",
"system": "System prompt",
"model": "Model ID (លំនាំដើម៖ auto)",
"max_tokens": "ចំនួន token អតិបរមាក្នុងការឆ្លើយតប",
"temperature": "សីតុណ្ហភាព sampling (02)",
@@ -256,7 +256,6 @@
"max_restarts": "ចំនួនអតិបរមានៃការចាប់ផ្តើមឡើងវិញបន្ទាប់ពីគាំងក្នុងរយៈពេល 30s មុនពេលបោះបង់ (លំនាំដើម៖ 2)",
"tray": "ចាប់ផ្ដើមក្នុងថាសប្រព័ន្ធ (សម្រាប់កុំព្យូទ័រលើតុប៉ុណ្ណោះ និងត្រូវជ្រើសរើសបើក)",
"no_tray": "បិទរូបតំណាងថាសប្រព័ន្ធ",
"ready_timeout": "រយៈពេលអស់ពេលនៃការត្រួតពិនិត្យភាពរួចរាល់ គិតជាមិល្លីវិនាទី (ក៏អាចប្រើ OMNIROUTE_READY_TIMEOUT_MS ផងដែរ, លំនាំដើម 60000)",
"tls_cert": "ផ្លូវទៅកាន់វិញ្ញាបនបត្រ TLS (PEM) សម្រាប់បម្រើ HTTPS (ក៏ជា OMNIROUTE_TLS_CERT ផងដែរ)",
"tls_key": "ផ្លូវទៅកាន់សោឯកជន TLS (PEM) សម្រាប់បម្រើ HTTPS (ក៏ជា OMNIROUTE_TLS_KEY ផងដែរ)"
},
@@ -348,16 +347,6 @@
"running": "ម៉ាស៊ីនមេ MCP កំពុងដំណើរការ ({transport})",
"stopped": "ម៉ាស៊ីនមេ MCP បានឈប់ដំណើរការ។",
"restarted": "ម៉ាស៊ីនមេ MCP បានចាប់ផ្តើមឡើងវិញ។",
"stoppedHint": "ដំណើរការ `omniroute mcp enable` ដើម្បីបើកវា។",
"enabled": "ម៉ាស៊ីនមេ MCP ត្រូវបានបើក។",
"disabled": "ម៉ាស៊ីនមេ MCP ត្រូវបានបិទ។",
"enable": {
"description": "បើកម៉ាស៊ីនមេ MCP",
"transport": "មធ្យោបាយបញ្ជូនដែលត្រូវប្រើ៖ stdio|sse|streamable-http"
},
"disable": {
"description": "បិទម៉ាស៊ីនមេ MCP"
},
"call": {
"description": "ហៅប្រើឧបករណ៍ MCP ដោយផ្ទាល់",
"args": "អាប់ជិចអាគុយម៉ង់ JSON (ក្នុងបន្ទាត់)",

View File

@@ -256,7 +256,6 @@
"max_restarts": "ಬಿಟ್ಟುಕೊಡುವ ಮೊದಲು 30s ಒಳಗೆ ಕ್ರ್ಯಾಶ್ನಿಂದ ಗರಿಷ್ಠ ಮರುಪ್ರಾರಂಭಗಳ ಸಂಖ್ಯೆ (ಡೀಫಾಲ್ಟ್: 2)",
"tray": "ಸಿಸ್ಟಮ್ ಟ್ರೇಯಲ್ಲಿ ಪ್ರಾರಂಭಿಸಿ (ಡೆಸ್ಕ್ಟಾಪ್ ಮಾತ್ರ, ಆಯ್ಕೆ ಮಾಡಿದಲ್ಲಿ)",
"no_tray": "ಸಿಸ್ಟಮ್ ಟ್ರೇ ಐಕಾನ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ",
"ready_timeout": "ಸಿದ್ಧತಾ ಪ್ರೋಬ್ ಕಾಲಮಿತಿ ಮಿಲಿಸೆಕೆಂಡುಗಳಲ್ಲಿ (OMNIROUTE_READY_TIMEOUT_MS ಕೂಡ, ಡೀಫಾಲ್ಟ್ 60000)",
"tls_cert": "HTTPS ಒದಗಿಸಲು TLS ಪ್ರಮಾಣಪತ್ರದ (PEM) ಪಥ (OMNIROUTE_TLS_CERT ಸಹ)",
"tls_key": "HTTPS ಒದಗಿಸಲು TLS ಖಾಸಗಿ ಕೀಲಿಯ (PEM) ಪಥ (OMNIROUTE_TLS_KEY ಸಹ)"
},
@@ -348,16 +347,6 @@
"running": "MCP ಸರ್ವರ್ ಚಾಲನೆಯಲ್ಲಿದೆ ({transport})",
"stopped": "MCP ಸರ್ವರ್ ನಿಲ್ಲಿಸಲಾಗಿದೆ.",
"restarted": "MCP ಸರ್ವರ್ ಮರುಪ್ರಾರಂಭಿಸಲಾಗಿದೆ.",
"stoppedHint": "ಇದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು `omniroute mcp enable` ಅನ್ನು ಚಲಾಯಿಸಿ.",
"enabled": "MCP ಸರ್ವರ್ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ.",
"disabled": "MCP ಸರ್ವರ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ.",
"enable": {
"description": "MCP ಸರ್ವರ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ",
"transport": "ಬಳಸಬೇಕಾದ ಟ್ರಾನ್ಸ್ಪೋರ್ಟ್: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP ಸರ್ವರ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"
},
"call": {
"description": "MCP ಟೂಲ್ ಅನ್ನು ನೇರವಾಗಿ ಆಹ್ವಾನಿಸಿ",
"args": "JSON ಆರ್ಗ್ಯುಮೆಂಟ್ಗಳ ಆಬ್ಜೆಕ್ಟ್ (ಇನ್ಲೈನ್)",

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Didžiausias paleidimų iš naujo po strigties skaičius per 30 s prieš nutraukiant bandymus (numatyta: 2)",
"tray": "Paleisti sistemos dėkle (tik darbalaukio programoje, pasirenkama)",
"no_tray": "Išjungti sistemos dėklo piktogramą",
"ready_timeout": "Parengties patikros skirtasis laikas ms (taip pat OMNIROUTE_READY_TIMEOUT_MS, numatytoji reikšmė 60000)",
"tls_cert": "Kelias į TLS sertifikatą (PEM), skirtą HTTPS teikti (taip pat OMNIROUTE_TLS_CERT)",
"tls_key": "Kelias į privatųjį TLS raktą (PEM), skirtą HTTPS teikti (taip pat OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "MCP serveris veikia ({transport})",
"stopped": "MCP serveris sustabdytas.",
"restarted": "MCP serveris paleistas iš naujo.",
"stoppedHint": "Norėdami jį įjungti, paleiskite `omniroute mcp enable`.",
"enabled": "MCP serveris įjungtas.",
"disabled": "MCP serveris išjungtas.",
"enable": {
"description": "Įjungti MCP serverį",
"transport": "Naudotinas perdavimo būdas: stdio|sse|streamable-http"
},
"disable": {
"description": "Išjungti MCP serverį"
},
"call": {
"description": "Tiesiogiai iškviesti MCP įrankį",
"args": "JSON argumentų objektas (įterptasis)",

View File

@@ -256,7 +256,6 @@
"max_restarts": "Maksimālās avāriju restartēšanas 30 sekunžu laikā pirms padoties (noklusējums: 2)",
"tray": "Sākt sistēmas tray (tikai darbvirsmas, izvēlēties)",
"no_tray": "Atspējot sistēmas tray ikonu",
"ready_timeout": "Gatavības pārbaudes taimauts milisekundēs (arī OMNIROUTE_READY_TIMEOUT_MS, noklusējuma vērtība 60000)",
"tls_cert": "Ceļš uz TLS sertifikātu (PEM) HTTPS apkalpošanai (arī OMNIROUTE_TLS_CERT)",
"tls_key": "Ceļš uz TLS privāto atslēgu (PEM) HTTPS apkalpošanai (arī OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "MCP serveris darbojas ({transport})",
"stopped": "MCP serveris apturēts.",
"restarted": "MCP serveris restartēts.",
"stoppedHint": "Lai to ieslēgtu, palaidiet `omniroute mcp enable`.",
"enabled": "MCP serveris ir iespējots.",
"disabled": "MCP serveris ir atspējots.",
"enable": {
"description": "Iespējot MCP serveri",
"transport": "Izmantojamais transports: stdio|sse|streamable-http"
},
"disable": {
"description": "Atspējot MCP serveri"
},
"call": {
"description": "Izsaukt MCP rīku tieši",
"args": "JSON argumentu objekts (iekšējais)",

View File

@@ -256,7 +256,6 @@
"max_restarts": "ഉപേക്ഷിക്കുന്നതിന് മുമ്പ് 30s-നുള്ളിൽ അനുവദിക്കുന്ന പരമാവധി ക്രാഷ് പുനരാരംഭങ്ങൾ (ഡിഫോൾട്ട്: 2)",
"tray": "സിസ്റ്റം ട്രേയിൽ ആരംഭിക്കുക (ഡെസ്ക്ടോപ്പിൽ മാത്രം, സ്വമേധയാ തിരഞ്ഞെടുക്കാവുന്നത്)",
"no_tray": "സിസ്റ്റം ട്രേ ഐക്കൺ പ്രവർത്തനരഹിതമാക്കുക",
"ready_timeout": "റെഡിനസ് പ്രോബ് സമയപരിധി ms-ൽ (OMNIROUTE_READY_TIMEOUT_MS എന്നതും, സ്ഥിരസ്ഥിതി 60000)",
"tls_cert": "HTTPS നൽകുന്നതിനുള്ള TLS സർട്ടിഫിക്കറ്റിന്റെ (PEM) പാത (OMNIROUTE_TLS_CERT-ലും)",
"tls_key": "HTTPS നൽകുന്നതിനുള്ള TLS സ്വകാര്യ കീയുടെ (PEM) പാത (OMNIROUTE_TLS_KEY-ലും)"
},
@@ -348,16 +347,6 @@
"running": "MCP സെർവർ പ്രവർത്തിക്കുന്നു ({transport})",
"stopped": "MCP സെർവർ നിർത്തി.",
"restarted": "MCP സെർവർ പുനരാരംഭിച്ചു.",
"stoppedHint": "ഇത് പ്രവർത്തനക്ഷമമാക്കാൻ `omniroute mcp enable` പ്രവർത്തിപ്പിക്കുക.",
"enabled": "MCP സെർവർ പ്രവർത്തനക്ഷമമാക്കി.",
"disabled": "MCP സെർവർ പ്രവർത്തനരഹിതമാക്കി.",
"enable": {
"description": "MCP സെർവർ പ്രവർത്തനക്ഷമമാക്കുക",
"transport": "ഉപയോഗിക്കേണ്ട ട്രാൻസ്പോർട്ട്: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP സെർവർ പ്രവർത്തനരഹിതമാക്കുക"
},
"call": {
"description": "ഒരു MCP ടൂൾ നേരിട്ട് പ്രവർത്തിപ്പിക്കുക",
"args": "JSON ആർഗ്യുമെന്റുകളുടെ ഒബ്ജക്റ്റ് (ഇൻലൈൻ)",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "L-ogħla numru ta' restarts wara crash fi żmien 30s qabel ma jċedi (default: 2)",
"tray": "Bda' fil-caffettar tal-pajjiż (biss desktop, għażla)",
"no_tray": "Iddiżattiva l-ikona tal-caffettar tal-pajjiż",
"ready_timeout": "Limitu taż-żmien tal-verifika tat-tħejjija f'ms (ukoll OMNIROUTE_READY_TIMEOUT_MS, valur predefinit 60000)",
"tls_cert": "Triq għal ċertifikat TLS (PEM) biex isservi HTTPS (ukoll OMNIROUTE_TLS_CERT)",
"tls_key": "Triq għas-sieqa privata tal-TLS (PEM) biex isservi HTTPS (ukoll OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "Servier MCP qed jaħdem ({transport})",
"stopped": "Servier MCP waqaf.",
"restarted": "Servier MCP restartat.",
"stoppedHint": "Ħaddem `omniroute mcp enable` biex tattivah.",
"enabled": "Is-server MCP huwa attivat.",
"disabled": "Is-server MCP huwa diżattivat.",
"enable": {
"description": "Attiva s-server MCP",
"transport": "Trasport li għandu jintuża: stdio|sse|streamable-http"
},
"disable": {
"description": "Iddiżattiva s-server MCP"
},
"call": {
"description": "Sejjaħ għodda MCP direttament",
"args": "Oġġett argomenti JSON (internament)",

View File

@@ -26,7 +26,7 @@
"testFailed": "Provider စမ်းသပ်မှု မအောင်မြင်ပါ: {error}",
"loginEnabled": "လော့ဂ်အင်: ဖွင့်ထားသည် (စကားဝှက်ကို အပ်ဒိတ်လုပ်ပြီး)",
"loginDisabled": "လော့ဂ်အင်: ပိတ်ထားသည်",
"providerInfo": "ပံ့ပိုးသူ: {info}",
"providerInfo": "Provider: {info}",
"opencode": "OpenCode အတွက် တွဲဖက်ပါရှိသော @omniroute/opencode-plugin ကို ထည့်သွင်းပြီး စီစဉ်သတ်မှတ်ရန်"
},
"doctor": {
@@ -41,7 +41,7 @@
"warnings": "သတိပေးချက် {count} ခု — အပေါ်တွင် ကြည့်ပါ။"
},
"providers": {
"title": "ပံ့ပိုးသူများ",
"title": "Providers",
"noProviders": "မည်သည့် provider ကိုမျှ စီစဉ်သတ်မှတ်မထားပါ။ လုပ်ဆောင်ရန်: omniroute setup",
"testing": "{name} ကို စမ်းသပ်နေသည်...",
"available": "အသုံးပြုနိုင်သော provider {count} ခု",
@@ -86,7 +86,7 @@
}
},
"keys": {
"title": "API သော့များ",
"title": "API Keys",
"addDescription": "ဝန်ဆောင်မှုပေးသူတစ်ခုအတွက် API key ကို ထည့်ရန် သို့မဟုတ် အပ်ဒိတ်လုပ်ရန်",
"listDescription": "ပြင်ဆင်သတ်မှတ်ထားသော API key အားလုံးကို စာရင်းပြုစုရန်",
"removeDescription": "ဝန်ဆောင်မှုပေးသူတစ်ခုအတွက် API key ကို ဖယ်ရှားရန်",
@@ -152,7 +152,7 @@
"file": "ဖိုင်မှ prompt ကို ဖတ်ပါ",
"stdin": "stdin မှ prompt ကို ဖတ်ပါ",
"model": "Model ID (မူလသတ်မှတ်ချက်: auto)",
"system": "စနစ်ညွှန်ကြားချက်",
"system": "System prompt",
"combo": "သတ်မှတ်ထားသော combo တစ်ခုကို အမည်ဖြင့် မဖြစ်မနေ အသုံးပြုပါ",
"max_tokens": "Response အတွင်း အများဆုံး token အရေအတွက်",
"responses_api": "/v1/chat/completions အစား /v1/responses ကို အသုံးပြုပါ",
@@ -226,7 +226,7 @@
"description": "OmniRoute သို့ တစ်ကြိမ်တည်းသုံး chat prompt ပို့ရန်",
"file": "ဖိုင်မှ prompt ကို ဖတ်ရန်",
"stdin": "stdin မှ prompt ကို ဖတ်ရန်",
"system": "စနစ်ညွှန်ကြားချက်",
"system": "System prompt",
"model": "Model ID (မူလသတ်မှတ်ချက်: auto)",
"max_tokens": "တုံ့ပြန်ချက်တွင် ပါဝင်မည့် token အရေအတွက် အများဆုံး",
"temperature": "နမူနာရွေးချယ်မှု temperature (02)",
@@ -256,7 +256,6 @@
"max_restarts": "လက်လျှော့မတိုင်မီ 30s အတွင်း ပျက်သွား၍ ပြန်လည်စတင်နိုင်သည့် အများဆုံးအကြိမ်ရေ (မူလသတ်မှတ်ချက်: 2)",
"tray": "စနစ် tray တွင် စတင်ပါ (desktop အတွက်သာ၊ ရွေးချယ်ဖွင့်သုံးရန်)",
"no_tray": "စနစ် tray အိုင်ကွန်ကို ပိတ်ပါ",
"ready_timeout": "အဆင်သင့်ဖြစ်မှု စစ်ဆေးချက်၏ အချိန်ကုန်ဆုံးကာလကို ms ဖြင့် သတ်မှတ်ပါ (OMNIROUTE_READY_TIMEOUT_MS လည်းဖြစ်ပြီး မူလတန်ဖိုးမှာ 60000 ဖြစ်သည်)",
"tls_cert": "HTTPS ဖြင့် ဝန်ဆောင်မှုပေးရန် TLS certificate (PEM) သို့ လမ်းကြောင်း (OMNIROUTE_TLS_CERT လည်းဖြစ်သည်)",
"tls_key": "HTTPS ဖြင့် ဝန်ဆောင်မှုပေးရန် TLS private key (PEM) သို့ လမ်းကြောင်း (OMNIROUTE_TLS_KEY လည်းဖြစ်သည်)"
},
@@ -348,16 +347,6 @@
"running": "MCP ဆာဗာ လည်ပတ်နေသည် ({transport})",
"stopped": "MCP ဆာဗာ ရပ်တန့်သွားပါပြီ။",
"restarted": "MCP ဆာဗာကို ပြန်လည်စတင်ပြီးပါပြီ။",
"stoppedHint": "၎င်းကို ဖွင့်ရန် `omniroute mcp enable` ကို လုပ်ဆောင်ပါ။",
"enabled": "MCP ဆာဗာကို ဖွင့်ထားသည်။",
"disabled": "MCP ဆာဗာကို ပိတ်ထားသည်။",
"enable": {
"description": "MCP ဆာဗာကို ဖွင့်ရန်",
"transport": "အသုံးပြုမည့် ပို့ဆောင်မှုနည်းလမ်း: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP ဆာဗာကို ပိတ်ရန်"
},
"call": {
"description": "MCP ကိရိယာတစ်ခုကို တိုက်ရိုက် ခေါ်ယူအသုံးပြုရန်",
"args": "JSON အငြင်းပွားဖွယ်ရာ အရာဝတ္ထု (စာကြောင်းအတွင်း)",
@@ -644,7 +633,7 @@
"content": "Memory စာသားအကြောင်းအရာ",
"file": "ဖိုင်မှ အကြောင်းအရာကို ဖတ်ရန်",
"type": "Memory အမျိုးအစား (မူလသတ်မှတ်ချက်: user)",
"metadata": "JSON မက်တာဒေတာ အရာဝတ္ထု",
"metadata": "JSON metadata object",
"api_key": "API key နှင့် ချိတ်ဆက်ရန်"
},
"clear": {
@@ -678,7 +667,7 @@
},
"start": {
"description": "provider တစ်ခုအတွက် OAuth ခွင့်ပြုချက် flow ကို စတင်ရန်",
"provider": "ပံ့ပိုးသူ ID (gemini, copilot, cursor, …)",
"provider": "Provider ID (gemini, copilot, cursor, …)",
"no_browser": "URL ကိုသာ ဖော်ပြရန် — browser ကို မဖွင့်ပါနှင့်",
"import_system": "စက်တွင်း system config မှ အထောက်အထားများကို အလိုအလျောက် ထည့်သွင်းရန်",
"social": "Social login provider (google|github) — kiro အတွက် လိုအပ်သည်",
@@ -715,7 +704,7 @@
"prompt_file": "ဖိုင်မှ prompt ကို ဖတ်ရန်",
"repo": "လုပ်ဆောင်စရာအတွက် clone ပြုလုပ်မည့် repository URL",
"branch": "အသုံးပြုမည့် branch အမည်",
"metadata": "JSON မက်တာဒေတာ အရာဝတ္ထု"
"metadata": "JSON metadata object"
},
"list": {
"description": "agent လုပ်ဆောင်စရာများကို စာရင်းပြုရန်",
@@ -1279,7 +1268,7 @@
"description": "LLM ဖြင့် အပြန်အလှန် ဆွေးနွေးမှုအကြိမ်များစွာ ပြုလုပ်နိုင်သော REPL",
"model": "အသုံးပြုမည့် မော်ဒယ် (ပုံသေ- auto)",
"combo": "အသုံးပြုမည့် Combo အမည်",
"system": "စနစ်ညွှန်ကြားချက်",
"system": "System prompt",
"resume": "သိမ်းဆည်းထားသော session ကို အမည်ဖြင့် ပြန်လည်စတင်ရန်"
},
"plugin": {

View File

@@ -226,11 +226,11 @@
"description": "OmniRoute मा एकपटकको chat prompt पठाउनुहोस्",
"file": "file बाट prompt पढ्नुहोस्",
"stdin": "stdin बाट prompt पढ्नुहोस्",
"system": "प्रणाली प्रम्प्ट",
"system": "System prompt",
"model": "Model ID (पूर्वनिर्धारित: auto)",
"max_tokens": "response मा अधिकतम tokens",
"temperature": "sampling temperature (02)",
"top_p": "Top-p न्युक्लियस नमुना चयन",
"top_p": "Top-p nucleus sampling",
"reasoning_effort": "तर्क प्रयासको स्तर (low|medium|high)",
"thinking_budget": "विस्तारित सोचाइको token बजेट",
"combo": "नामद्वारा कुनै विशिष्ट combo प्रयोग गर्न बाध्य पार्नुहोस्",
@@ -256,7 +256,6 @@
"max_restarts": "प्रयास छोड्नुअघि 30s भित्र क्र्यासपछि पुनः सुरु गर्ने अधिकतम सङ्ख्या (पूर्वनिर्धारित: 2)",
"tray": "सिस्टम ट्रेमा सुरु गर्नुहोस् (डेस्कटपमा मात्र, स्वैच्छिक)",
"no_tray": "सिस्टम ट्रे आइकन असक्षम गर्नुहोस्",
"ready_timeout": "मिलिसेकेन्डमा तत्परता प्रोबको समयसीमा (OMNIROUTE_READY_TIMEOUT_MS पनि, पूर्वनिर्धारित 60000)",
"tls_cert": "HTTPS सेवा दिन TLS प्रमाणपत्र (PEM) को पथ (OMNIROUTE_TLS_CERT पनि)",
"tls_key": "HTTPS सेवा दिन TLS निजी कुञ्जी (PEM) को पथ (OMNIROUTE_TLS_KEY पनि)"
},
@@ -348,16 +347,6 @@
"running": "MCP सर्भर चलिरहेको छ ({transport})",
"stopped": "MCP सर्भर रोकियो।",
"restarted": "MCP सर्भर पुनः सुरु गरियो।",
"stoppedHint": "यसलाई सक्रिय गर्न `omniroute mcp enable` चलाउनुहोस्।",
"enabled": "MCP सर्भर सक्रिय गरियो।",
"disabled": "MCP सर्भर निष्क्रिय गरियो।",
"enable": {
"description": "MCP सर्भर सक्रिय गर्नुहोस्",
"transport": "प्रयोग गर्ने ट्रान्सपोर्ट: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP सर्भर निष्क्रिय गर्नुहोस्"
},
"call": {
"description": "MCP उपकरणलाई सिधै आह्वान गर्नुहोस्",
"args": "JSON आर्गुमेन्ट अब्जेक्ट (इनलाइन)",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "ହାର ମାନିବା ପୂର୍ବରୁ 30s ମଧ୍ୟରେ ସର୍ବାଧିକ କ୍ରାଶ୍ ପୁନଃଆରମ୍ଭ ସଂଖ୍ୟା (ଡିଫଲ୍ଟ: 2)",
"tray": "ସିଷ୍ଟମ୍ ଟ୍ରେରେ ଆରମ୍ଭ କରନ୍ତୁ (କେବଳ ଡେସ୍କଟପ୍, ଇଚ୍ଛାଧୀନ)",
"no_tray": "ସିଷ୍ଟମ୍ ଟ୍ରେ ଆଇକନ୍ ଅକ୍ଷମ କରନ୍ତୁ",
"ready_timeout": "ମିଲିସେକେଣ୍ଡରେ ପ୍ରସ୍ତୁତି ପ୍ରୋବ୍ର ସମୟସୀମା (OMNIROUTE_READY_TIMEOUT_MS ମଧ୍ୟ, ଡିଫଲ୍ଟ 60000)",
"tls_cert": "HTTPS ପରିବେଷଣ ପାଇଁ TLS ସର୍ଟିଫିକେଟ୍ (PEM)ର ପଥ (OMNIROUTE_TLS_CERT ମଧ୍ୟ)",
"tls_key": "HTTPS ପରିବେଷଣ ପାଇଁ TLS ବ୍ୟକ୍ତିଗତ କୀ (PEM)ର ପଥ (OMNIROUTE_TLS_KEY ମଧ୍ୟ)"
},
@@ -348,16 +347,6 @@
"running": "MCP ସର୍ଭର ଚାଲୁଛି ({transport})",
"stopped": "MCP ସର୍ଭର ବନ୍ଦ ହୋଇଛି।",
"restarted": "MCP ସର୍ଭର ପୁନଃଚାଲୁ ହୋଇଛି।",
"stoppedHint": "ଏହାକୁ ସକ୍ରିୟ କରିବା ପାଇଁ `omniroute mcp enable` ଚଲାନ୍ତୁ।",
"enabled": "MCP ସର୍ଭର୍ ସକ୍ରିୟ ହୋଇଛି।",
"disabled": "MCP ସର୍ଭର୍ ନିଷ୍କ୍ରିୟ ହୋଇଛି।",
"enable": {
"description": "MCP ସର୍ଭର୍କୁ ସକ୍ରିୟ କରନ୍ତୁ",
"transport": "ବ୍ୟବହାର କରିବାକୁ ଥିବା ପରିବହନ: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP ସର୍ଭର୍କୁ ନିଷ୍କ୍ରିୟ କରନ୍ତୁ"
},
"call": {
"description": "ସିଧାସଳଖ ଏକ MCP ଟୁଲ୍ ଆହ୍ୱାନ କରନ୍ତୁ",
"args": "JSON ଆର୍ଗୁମେଣ୍ଟ ଅବଜେକ୍ଟ (ଇନ୍‌ଲାଇନ୍)",

View File

@@ -256,7 +256,6 @@
"max_restarts": "ਹਾਰ ਮੰਨਣ ਤੋਂ ਪਹਿਲਾਂ 30s ਦੇ ਅੰਦਰ ਕ੍ਰੈਸ਼ ਤੋਂ ਬਾਅਦ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਵੱਧ ਤੋਂ ਵੱਧ ਗਿਣਤੀ (ਡਿਫਾਲਟ: 2)",
"tray": "ਸਿਸਟਮ ਟਰੇ ਵਿੱਚ ਸ਼ੁਰੂ ਕਰੋ (ਸਿਰਫ਼ ਡੈਸਕਟਾਪ, ਚੋਣਵਾਂ)",
"no_tray": "ਸਿਸਟਮ ਟਰੇ ਆਈਕਨ ਅਸਮਰੱਥ ਕਰੋ",
"ready_timeout": "ਰੈਡੀਨੈੱਸ ਪ੍ਰੋਬ ਦਾ ਟਾਈਮਆਉਟ ਮਿਲੀਸਕਿੰਟਾਂ ਵਿੱਚ (OMNIROUTE_READY_TIMEOUT_MS ਵੀ, ਡਿਫਾਲਟ 60000)",
"tls_cert": "HTTPS ਸਰਵ ਕਰਨ ਲਈ TLS ਸਰਟੀਫਿਕੇਟ (PEM) ਦਾ ਪਾਥ (OMNIROUTE_TLS_CERT ਵੀ)",
"tls_key": "HTTPS ਸਰਵ ਕਰਨ ਲਈ TLS ਨਿੱਜੀ ਕੁੰਜੀ (PEM) ਦਾ ਪਾਥ (OMNIROUTE_TLS_KEY ਵੀ)"
},
@@ -348,16 +347,6 @@
"running": "MCP ਸਰਵਰ ਚੱਲ ਰਿਹਾ ਹੈ ({transport})",
"stopped": "MCP ਸਰਵਰ ਰੋਕਿਆ ਗਿਆ।",
"restarted": "MCP ਸਰਵਰ ਮੁੜ ਚਾਲੂ ਕੀਤਾ ਗਿਆ।",
"stoppedHint": "ਇਸਨੂੰ ਚਾਲੂ ਕਰਨ ਲਈ `omniroute mcp enable` ਚਲਾਓ।",
"enabled": "MCP ਸਰਵਰ ਚਾਲੂ ਹੈ।",
"disabled": "MCP ਸਰਵਰ ਬੰਦ ਹੈ।",
"enable": {
"description": "MCP ਸਰਵਰ ਚਾਲੂ ਕਰੋ",
"transport": "ਵਰਤਣ ਲਈ ਟ੍ਰਾਂਸਪੋਰਟ: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP ਸਰਵਰ ਬੰਦ ਕਰੋ"
},
"call": {
"description": "ਕਿਸੇ MCP ਟੂਲ ਨੂੰ ਸਿੱਧੇ ਚਲਾਓ",
"args": "JSON ਆਰਗੂਮੈਂਟ ਆਬਜੈਕਟ (ਇਨਲਾਈਨ)",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@
"opencode": "Instala e configura o plugin @omniroute/opencode-plugin incluído para o OpenCode"
},
"doctor": {
"title": "Diagnóstico do OmniRoute",
"title": "OmniRoute Doctor",
"dbOk": "Banco de dados: OK ({path})",
"dbMissing": "Banco de dados: não inicializado — execute `omniroute setup`",
"portOk": "Porta {port}: disponível",
@@ -256,7 +256,6 @@
"max_restarts": "Máximo de reinícios em 30s antes de desistir (padrão: 2)",
"tray": "Mostrar ícone na bandeja do sistema (apenas desktop, opt-in)",
"no_tray": "Desabilitar ícone na bandeja do sistema",
"ready_timeout": "Tempo limite da verificação de prontidão em ms (também OMNIROUTE_READY_TIMEOUT_MS, padrão 60000)",
"tls_cert": "Caminho para um certificado TLS (PEM) para servir HTTPS (também OMNIROUTE_TLS_CERT)",
"tls_key": "Caminho para a chave privada TLS (PEM) para servir HTTPS (também OMNIROUTE_TLS_KEY)"
},
@@ -302,7 +301,7 @@
"noServer": "Servidor não está em execução. Inicie com: omniroute serve",
"title": "Saúde",
"status": "Status: {status}",
"uptime": "Tempo de atividade: {uptime}",
"uptime": "Uptime: {uptime}",
"requests": "Requisições (24h): {count}",
"cost": "Custo (24h): ${cost}"
},
@@ -345,19 +344,6 @@
},
"mcp": {
"title": "Servidor MCP",
"running": "Servidor MCP em execução ({transport})",
"stopped": "Servidor MCP parado.",
"restarted": "Servidor MCP reiniciado.",
"stoppedHint": "Execute `omniroute mcp enable` para ativá-lo.",
"enabled": "Servidor MCP ativado.",
"disabled": "Servidor MCP desativado.",
"enable": {
"description": "Ativar o servidor MCP",
"transport": "Transporte a ser usado: stdio|sse|streamable-http"
},
"disable": {
"description": "Desativar o servidor MCP"
},
"call": {
"description": "Invocar uma ferramenta MCP diretamente",
"args": "Objeto JSON de argumentos (inline)",
@@ -385,7 +371,10 @@
},
"audit": {
"description": "Log de auditoria MCP (alias para audit --source mcp)"
}
},
"running": "Servidor MCP em execução ({transport})",
"stopped": "Servidor MCP parado.",
"restarted": "Servidor MCP reiniciado."
},
"a2a": {
"skills": {
@@ -1104,7 +1093,7 @@
}
},
"combo": {
"title": "Combinações",
"title": "Combos",
"switched": "Combo ativo: {name}",
"created": "Combo criado: {name}",
"deleted": "Combo removido: {name}",
@@ -1279,7 +1268,7 @@
"description": "REPL interativo multi-turn com LLM",
"model": "Modelo a usar (padrão: auto)",
"combo": "Nome do combo a usar",
"system": "Prompt do sistema",
"system": "System prompt",
"resume": "Retomar sessão salva pelo nome"
},
"plugin": {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "අත්හැරීමට පෙර තත්පර 30ක් තුළ සිදු කළ හැකි උපරිම බිඳවැටීම්-පසු නැවත ආරම්භ කිරීම් ගණන (පෙරනිමිය: 2)",
"tray": "පද්ධති ට්රේ තුළ ආරම්භ කරන්න (ඩෙස්ක්ටොප් සඳහා පමණි, කැමැත්තෙන් සක්රිය කළ යුතුය)",
"no_tray": "පද්ධති ට්රේ නිරූපකය අක්රිය කරන්න",
"ready_timeout": "සූදානම්දැයි පරීක්ෂා කිරීමේ කල් ඉකුත්වීම ms වලින් (OMNIROUTE_READY_TIMEOUT_MS ද අදාළ වේ, පෙරනිමිය 60000)",
"tls_cert": "HTTPS සේවය සැපයීම සඳහා TLS සහතිකයකට (PEM) ඇති මාර්ගය (OMNIROUTE_TLS_CERT ද අදාළ වේ)",
"tls_key": "HTTPS සේවය සැපයීම සඳහා TLS පුද්ගලික යතුරට (PEM) ඇති මාර්ගය (OMNIROUTE_TLS_KEY ද අදාළ වේ)"
},
@@ -348,16 +347,6 @@
"running": "MCP සේවාදායකය ක්රියාත්මකයි ({transport})",
"stopped": "MCP සේවාදායකය නවතා ඇත.",
"restarted": "MCP සේවාදායකය නැවත ආරම්භ කරන ලදී.",
"stoppedHint": "එය සක්රීය කිරීමට `omniroute mcp enable` ධාවනය කරන්න.",
"enabled": "MCP සේවාදායකය සක්රීය කර ඇත.",
"disabled": "MCP සේවාදායකය අක්රීය කර ඇත.",
"enable": {
"description": "MCP සේවාදායකය සක්රීය කරන්න",
"transport": "භාවිත කළ යුතු ප්රවාහන ක්රමය: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP සේවාදායකය අක්රීය කරන්න"
},
"call": {
"description": "MCP මෙවලමක් සෘජුවම කැඳවන්න",
"args": "JSON තර්ක වස්තුව (පේළිය තුළ)",
@@ -1104,7 +1093,7 @@
}
},
"combo": {
"title": "සංයෝජන",
"title": "Combos",
"switched": "සක්රිය combo එක: {name}",
"created": "Combo එක සාදන ලදී: {name}",
"deleted": "Combo එක මකා දමන ලදී: {name}",

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Največje število ponovnih zagonov po sesutju v 30 s, preden se poskušanje opusti (privzeto: 2)",
"tray": "Zaženi v sistemski vrstici (samo za namizne sisteme, po izbiri)",
"no_tray": "Onemogoči ikono sistemske vrstice",
"ready_timeout": "Časovna omejitev preverjanja pripravljenosti v ms (tudi OMNIROUTE_READY_TIMEOUT_MS, privzeto 60000)",
"tls_cert": "Pot do potrdila TLS (PEM) za streženje prek HTTPS (tudi OMNIROUTE_TLS_CERT)",
"tls_key": "Pot do zasebnega ključa TLS (PEM) za streženje prek HTTPS (tudi OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "Strežnik MCP deluje ({transport})",
"stopped": "Strežnik MCP je ustavljen.",
"restarted": "Strežnik MCP je znova zagnan.",
"stoppedHint": "Za vklop zaženite `omniroute mcp enable`.",
"enabled": "Strežnik MCP je omogočen.",
"disabled": "Strežnik MCP je onemogočen.",
"enable": {
"description": "Omogoči strežnik MCP",
"transport": "Prenos, ki naj se uporabi: stdio|sse|streamable-http"
},
"disable": {
"description": "Onemogoči strežnik MCP"
},
"call": {
"description": "Neposredno prikliči orodje MCP",
"args": "Objekt argumentov JSON (v vrstici)",

View File

@@ -256,7 +256,6 @@
"max_restarts": "Maksimalan broj ponovnih pokretanja pri padu u toku 30s pre odustajanja (podrazumevano: 2)",
"tray": "Покретање у системској траци (само десктоп, опционо)",
"no_tray": "Онемогући икону у системској траци",
"ready_timeout": "Временско ограничење провере спремности у ms (такође OMNIROUTE_READY_TIMEOUT_MS, подразумевано 60000)",
"tls_cert": "Путања до TLS сертификата (PEM) за HTTPS (такође OMNIROUTE_TLS_CERT)",
"tls_key": "Путања до TLS приватног кључа (PEM) за HTTPS (такође OMNIROUTE_TLS_KEY)"
},
@@ -301,7 +300,7 @@
"description": "Провери здравље сервера и статус компоненти",
"noServer": "Сервер није покренут. Покрените са: omniroute serve",
"title": "Здравље",
"status": "Статус: {status}",
"status": "Status: {status}",
"uptime": "Vreme rada: {uptime}",
"requests": "Zahtevi (24h): {count}",
"cost": "Trošak (24h): ${cost}"
@@ -348,16 +347,6 @@
"running": "MCP server je pokrenut ({transport})",
"stopped": "MCP server je zaustavljen.",
"restarted": "MCP server je ponovo pokrenut.",
"stoppedHint": "Покрените `omniroute mcp enable` да бисте га укључили.",
"enabled": "MCP сервер је омогућен.",
"disabled": "MCP сервер је онемогућен.",
"enable": {
"description": "Омогући MCP сервер",
"transport": "Протокол за пренос: stdio|sse|streamable-http"
},
"disable": {
"description": "Онемогући MCP сервер"
},
"call": {
"description": "Direktno pozovi MCP alat",
"args": "JSON objekat argumenata (inline)",
@@ -821,7 +810,7 @@
}
},
"program": {
"description": "OmniRoute — паметни AI рутер са аутоматским пребацивањем на резервну опцију",
"description": "OmniRoute — Smart AI Router with Auto Fallback",
"version": "Прикажи верзију и изађи",
"output": "Формат излаза (table, json, jsonl, csv)",
"quiet": "Сакрий небитан излаз",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Taslim bolishdan oldin 30 soniya ichidagi nosozlikdan keyingi qayta ishga tushirishlarning maksimal soni (standart: 2)",
"tray": "Tizim treyida ishga tushirish (faqat ish stoli versiyasida, ixtiyoriy)",
"no_tray": "Tizim treyi belgisini ochirish",
"ready_timeout": "Tayyorlik tekshiruvi uchun kutish vaqti, ms (shuningdek OMNIROUTE_READY_TIMEOUT_MS, standart qiymat 60000)",
"tls_cert": "HTTPS orqali xizmat korsatish uchun TLS sertifikati (PEM) yoli (shuningdek, OMNIROUTE_TLS_CERT)",
"tls_key": "HTTPS orqali xizmat korsatish uchun TLS maxfiy kaliti (PEM) yoli (shuningdek, OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "MCP serveri ishlamoqda ({transport})",
"stopped": "MCP serveri toʻxtatildi.",
"restarted": "MCP serveri qayta ishga tushirildi.",
"stoppedHint": "Uni yoqish uchun `omniroute mcp enable` buyrugʻini ishga tushiring.",
"enabled": "MCP serveri yoqildi.",
"disabled": "MCP serveri oʻchirildi.",
"enable": {
"description": "MCP serverini yoqish",
"transport": "Foydalaniladigan transport: stdio|sse|streamable-http"
},
"disable": {
"description": "MCP serverini oʻchirish"
},
"call": {
"description": "MCP vositasini bevosita chaqirish",
"args": "JSON argumentlar obyekti (ichki)",

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,6 @@
"max_restarts": "Iye ìtunbẹ̀rẹ̀ lẹ́yìn ìkọ̀lù tó pọ̀ jù láàárín 30s kí ó tó jáwọ́ (àìyípadà: 2)",
"tray": "Bẹ̀rẹ̀ nínú atẹ eto (dẹ́sítọ́ọ̀pù nìkan, àṣàyàn ni)",
"no_tray": "Pa àmì atẹ eto rẹ́",
"ready_timeout": "Àkókò ìdádúró tó pọ̀ jù fún àyẹ̀wò ìmúrasílẹ̀ ní ms (bákan náà OMNIROUTE_READY_TIMEOUT_MS, àìyípadà 60000)",
"tls_cert": "Ọ̀nà sí ìwé-ẹ̀rí TLS kan (PEM) láti pèsè HTTPS (bákan náà OMNIROUTE_TLS_CERT)",
"tls_key": "Ọ̀nà sí kọ́kọ́rọ́ àdáni TLS (PEM) láti pèsè HTTPS (bákan náà OMNIROUTE_TLS_KEY)"
},
@@ -348,16 +347,6 @@
"running": "Olùpín MCP ń ṣiṣẹ́ ({transport})",
"stopped": "Olùpín MCP ti dá iṣẹ́ dúró.",
"restarted": "A ti tún olùpín MCP bẹ̀rẹ̀.",
"stoppedHint": "Ṣe `omniroute mcp enable` láti mú un ṣiṣẹ́.",
"enabled": "A ti mú olupin MCP ṣiṣẹ́.",
"disabled": "A ti pa olupin MCP.",
"enable": {
"description": "Mú olupin MCP ṣiṣẹ́",
"transport": "Ọ̀nà ìgbéṣùmọ̀ tí a ó lò: stdio|sse|streamable-http"
},
"disable": {
"description": "Pa olupin MCP"
},
"call": {
"description": "Pe ohun èlò MCP kan ní tààrà",
"args": "Ohun àríyànjiyàn JSON (níbẹ̀-níbẹ̀)",

View File

@@ -14,6 +14,18 @@
"jsonOpt": "以 JSON 格式输出",
"yesOpt": "跳过确认"
},
"program": {
"description": "OmniRoute — 具有自动故障转移的智能 AI 路由器",
"version": "打印版本并退出",
"output": "输出格式table, json, jsonl, csv",
"quiet": "禁止非必要输出",
"no_color": "禁用彩色输出",
"timeout": "HTTP 请求超时(毫秒)",
"api_key": "OmniRoute 服务器的 API 密钥",
"base_url": "OmniRoute 服务器的基础 URL",
"context": "此命令使用的服务器上下文/配置文件",
"lang": "设置 CLI 显示语言(覆盖 OMNIROUTE_LANG"
},
"setup": {
"title": "OmniRoute 设置",
"passwordPrompt": "管理员密码",
@@ -133,20 +145,6 @@
"listTitle": "{days} 天内过期的密钥:"
}
},
"authExport": {
"description": "导出已解密的提供者凭据(仅限本地,明文输出)",
"idOpt": "仅导出与此 id/名称/提供者匹配的连接",
"formatOpt": "输出格式json 或 env",
"outOpt": "将输出写入文件而非标准输出(以 0600 权限写入)",
"forceOpt": "确认你了解此操作会打印/写入明文密钥",
"warning": "⚠ 此操作会打印/写入已解密的明文 API 密钥和 OAuth 令牌。请确保你的屏幕、shell 历史记录以及任何输出文件保持私密。",
"confirmHeading": "⚠ 警告:此操作会以明文导出已解密的提供者凭据",
"confirmBody": "此命令会为所选连接解密并打印/写入 apiKey、accessToken、refreshToken 和\nidToken。请将输出视为机密。",
"confirmFooter": "如需确认,请运行:\n omniroute auth export --force",
"missingKey": "导出凭据需要 STORAGE_ENCRYPTION_KEY。",
"notFound": "未找到连接:{id}",
"invalidFormat": "无效格式:{format}。请使用 json 或 env。"
},
"stream": {
"description": "使用 SSE 检查模式流式传输聊天响应",
"file": "从文件读取提示",
@@ -304,7 +302,7 @@
"status": "状态:{status}",
"uptime": "运行时间:{uptime}",
"requests": "请求数24h{count}",
"cost": "成本24h${cost}"
"cost": "成本24h"
},
"quota": {
"description": "显示提供者配额使用情况",
@@ -348,16 +346,6 @@
"running": "MCP 服务器正在运行({transport}",
"stopped": "MCP 服务器已停止。",
"restarted": "MCP 服务器已重启。",
"stoppedHint": "运行 `omniroute mcp enable` 以启用它。",
"enabled": "MCP 服务器已启用。",
"disabled": "MCP 服务器已禁用。",
"enable": {
"description": "启用 MCP 服务器",
"transport": "要使用的传输方式stdio|sse|streamable-http"
},
"disable": {
"description": "禁用 MCP 服务器"
},
"call": {
"description": "直接调用 MCP 工具",
"args": "JSON 参数对象(内联)",
@@ -820,18 +808,6 @@
"event": "要模拟的事件类型默认request.completed"
}
},
"program": {
"description": "OmniRoute — 具有自动故障转移的智能 AI 路由器",
"version": "打印版本并退出",
"output": "输出格式table, json, jsonl, csv",
"quiet": "禁止非必要输出",
"no_color": "禁用彩色输出",
"timeout": "HTTP 请求超时(毫秒)",
"api_key": "OmniRoute 服务器的 API 密钥",
"base_url": "OmniRoute 服务器的基础 URL",
"context": "此命令使用的服务器上下文/配置文件",
"lang": "设置 CLI 显示语言(覆盖 OMNIROUTE_LANG"
},
"files": {
"description": "管理文件(上传、列出、获取、下载、删除)",
"list": {
@@ -932,11 +908,6 @@
"model": "按模型筛选"
}
},
"radar": {
"description": "检查并同步本地 Radar 目录订阅源",
"status": "显示本地 Radar 设置和订阅源缓存状态",
"sync": "通过本地服务器同步目录、推荐、优惠和 Intel"
},
"resilience": {
"description": "检查和管理弹性机制",
"status": {
@@ -1264,8 +1235,8 @@
"description": "管理 OmniRoute 开机自启Linuxsystemd 用户服务)",
"enable": "启用开机自启",
"disable": "禁用开机自启",
"toggle": "切换开机自启",
"status": "显示自启状态"
"status": "显示自启状态",
"toggle": "切换开机自启"
},
"runtime": {
"description": "管理本地运行时依赖",
@@ -1292,6 +1263,25 @@
"update": "更新已安装的插件",
"scaffold": "搭建新的插件模板"
},
"authExport": {
"description": "导出已解密的提供者凭据(仅限本地,明文输出)",
"idOpt": "仅导出与此 id/名称/提供者匹配的连接",
"formatOpt": "输出格式json 或 env",
"outOpt": "将输出写入文件而非标准输出(以 0600 权限写入)",
"forceOpt": "确认你了解此操作会打印/写入明文密钥",
"warning": "⚠ 此操作会打印/写入已解密的明文 API 密钥和 OAuth 令牌。请确保你的屏幕、shell 历史记录以及任何输出文件保持私密。",
"confirmHeading": "⚠ 警告:此操作会以明文导出已解密的提供者凭据",
"confirmBody": "此命令会为所选连接解密并打印/写入 apiKey、accessToken、refreshToken 和\nidToken。请将输出视为机密。",
"confirmFooter": "如需确认,请运行:\n omniroute auth export --force",
"missingKey": "导出凭据需要 STORAGE_ENCRYPTION_KEY。",
"notFound": "未找到连接:{id}",
"invalidFormat": "无效格式:{format}。请使用 json 或 env。"
},
"radar": {
"description": "检查并同步本地 Radar 目录订阅源",
"status": "显示本地 Radar 设置和订阅源缓存状态",
"sync": "通过本地服务器同步目录、推荐、优惠和 Intel"
},
"launch": {
"description": "启动指向 OmniRoute 的 Claude Code本地或远程使用 --profile",
"token": "Claude 客户端应发送的令牌ANTHROPIC_AUTH_TOKEN",

View File

@@ -14,6 +14,18 @@
"jsonOpt": "以 JSON 格式輸出",
"yesOpt": "跳過確認"
},
"program": {
"description": "OmniRoute — 具有自動故障轉移的智慧 AI 路由器",
"version": "列印版本並退出",
"output": "輸出格式table, json, jsonl, csv",
"quiet": "禁止非必要輸出",
"no_color": "停用彩色輸出",
"timeout": "HTTP 請求超時(毫秒)",
"api_key": "OmniRoute 伺服器的 API 金鑰",
"base_url": "OmniRoute 伺服器的基礎 URL",
"context": "此命令使用的伺服器上下文/配置檔案",
"lang": "設定 CLI 顯示語言(覆蓋 OMNIROUTE_LANG"
},
"setup": {
"title": "OmniRoute 設定",
"passwordPrompt": "管理員密碼",
@@ -133,20 +145,6 @@
"listTitle": "{days} 天內過期的金鑰:"
}
},
"authExport": {
"description": "匯出已解密的提供者憑據(僅限本機,明文輸出)",
"idOpt": "僅匯出與此 id/名稱/提供者相符的連線",
"formatOpt": "輸出格式json 或 env",
"outOpt": "將輸出寫入檔案而非標準輸出(以 0600 權限寫入)",
"forceOpt": "確認你了解此操作會列印/寫入明文密鑰",
"warning": "⚠ 此操作會列印/寫入已解密的明文 API 金鑰和 OAuth 令牌。請確保你的螢幕、shell 歷史記錄以及任何輸出檔案保持私密。",
"confirmHeading": "⚠ 警告:此操作會以明文匯出已解密的提供者憑據",
"confirmBody": "此命令會為所選連線解密並列印/寫入 apiKey、accessToken、refreshToken 和\nidToken。請將輸出視為機密。",
"confirmFooter": "如需確認,請執行:\n omniroute auth export --force",
"missingKey": "匯出憑據需要 STORAGE_ENCRYPTION_KEY。",
"notFound": "找不到連線:{id}",
"invalidFormat": "無效格式:{format}。請使用 json 或 env。"
},
"stream": {
"description": "使用 SSE 檢查模式流式傳輸聊天響應",
"file": "從檔案讀取提示",
@@ -304,7 +302,7 @@
"status": "狀態:{status}",
"uptime": "執行時間:{uptime}",
"requests": "請求數24h{count}",
"cost": "成本24h${cost}"
"cost": "成本24h"
},
"quota": {
"description": "顯示提供者配額使用情況",
@@ -348,16 +346,6 @@
"running": "MCP 伺服器正在執行({transport}",
"stopped": "MCP 伺服器已停止。",
"restarted": "MCP 伺服器已重啟。",
"stoppedHint": "執行 `omniroute mcp enable` 以啟用它。",
"enabled": "MCP 伺服器已啟用。",
"disabled": "MCP 伺服器已停用。",
"enable": {
"description": "啟用 MCP 伺服器",
"transport": "要使用的傳輸方式stdio|sse|streamable-http"
},
"disable": {
"description": "停用 MCP 伺服器"
},
"call": {
"description": "直接呼叫 MCP 工具",
"args": "JSON 引數物件(內聯)",
@@ -820,18 +808,6 @@
"event": "要模擬的事件型別預設request.completed"
}
},
"program": {
"description": "OmniRoute — 具有自動故障轉移的智慧 AI 路由器",
"version": "列印版本並退出",
"output": "輸出格式table, json, jsonl, csv",
"quiet": "禁止非必要輸出",
"no_color": "停用彩色輸出",
"timeout": "HTTP 請求超時(毫秒)",
"api_key": "OmniRoute 伺服器的 API 金鑰",
"base_url": "OmniRoute 伺服器的基礎 URL",
"context": "此命令使用的伺服器上下文/配置檔案",
"lang": "設定 CLI 顯示語言(覆蓋 OMNIROUTE_LANG"
},
"files": {
"description": "管理檔案(上傳、列出、獲取、下載、刪除)",
"list": {
@@ -932,11 +908,6 @@
"model": "按模型篩選"
}
},
"radar": {
"description": "檢查並同步本機 Radar 目錄訂閱來源",
"status": "顯示本機 Radar 設定和訂閱來源快取狀態",
"sync": "透過本機伺服器同步目錄、推薦、優惠和 Intel"
},
"resilience": {
"description": "檢查和管理彈性機制",
"status": {
@@ -1264,8 +1235,8 @@
"description": "管理 OmniRoute 開機自啟Linuxsystemd 使用者服務)",
"enable": "啟用開機自啟",
"disable": "停用開機自啟",
"toggle": "切換開機自啟",
"status": "顯示自啟狀態"
"status": "顯示自啟狀態",
"toggle": "切換開機自啟"
},
"runtime": {
"description": "管理本地執行時依賴",
@@ -1292,6 +1263,25 @@
"update": "更新已安裝的外掛",
"scaffold": "搭建新的外掛模板"
},
"authExport": {
"description": "匯出已解密的提供者憑據(僅限本機,明文輸出)",
"idOpt": "僅匯出與此 id/名稱/提供者相符的連線",
"formatOpt": "輸出格式json 或 env",
"outOpt": "將輸出寫入檔案而非標準輸出(以 0600 權限寫入)",
"forceOpt": "確認你了解此操作會列印/寫入明文密鑰",
"warning": "⚠ 此操作會列印/寫入已解密的明文 API 金鑰和 OAuth 令牌。請確保你的螢幕、shell 歷史記錄以及任何輸出檔案保持私密。",
"confirmHeading": "⚠ 警告:此操作會以明文匯出已解密的提供者憑據",
"confirmBody": "此命令會為所選連線解密並列印/寫入 apiKey、accessToken、refreshToken 和\nidToken。請將輸出視為機密。",
"confirmFooter": "如需確認,請執行:\n omniroute auth export --force",
"missingKey": "匯出憑據需要 STORAGE_ENCRYPTION_KEY。",
"notFound": "找不到連線:{id}",
"invalidFormat": "無效格式:{format}。請使用 json 或 env。"
},
"radar": {
"description": "檢查並同步本機 Radar 目錄訂閱來源",
"status": "顯示本機 Radar 設定和訂閱來源快取狀態",
"sync": "透過本機伺服器同步目錄、推薦、優惠和 Intel"
},
"launch": {
"description": "啟動指向 OmniRoute 的 Claude Code本機或遠端使用 --profile",
"token": "Claude 用戶端應傳送的令牌ANTHROPIC_AUTH_TOKEN",

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