Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
8d50c9d
chore(release): open v3.8.36 development cycle
Jun 23, 2026
e494afe
refactor(chatCore): extrai resolveCompressionSettings (#3501) (#4826)
diegosouzapw Jun 23, 2026
e420c34
refactor(chatCore): extrai predicados puros de combo de compressão (#…
diegosouzapw Jun 23, 2026
34b510a
refactor(chatCore): extrai emitOutputStyleTelemetry (#3501) (#4811)
diegosouzapw Jun 23, 2026
812f2f2
refactor(chatCore): extrai writeCompressionAnalytics (bloco analytics…
diegosouzapw Jun 23, 2026
eb1920b
refactor(chatCore): extrai runPluginOnRequestHook (#3501) (#4827)
diegosouzapw Jun 23, 2026
e85fb9b
refactor(chatCore): extrai applyClientUsageBuffer (buffer/estimate de…
diegosouzapw Jun 23, 2026
5c0b29e
refactor(chatCore): extrai buildPostCallGuardrailContext (contexto gu…
diegosouzapw Jun 23, 2026
1244e49
refactor(chatCore): extrai storeSemanticCacheResponse (cache-store no…
diegosouzapw Jun 23, 2026
867e904
refactor(chatCore): extrai buildNonStreamingResponseHeaders (headers …
diegosouzapw Jun 23, 2026
c69bd6a
refactor(chatCore): extrai maybeConvertJsonBodyToSse (#3089 JSON→SSE …
diegosouzapw Jun 23, 2026
e87d35c
refactor(chatCore): extrai assembleStreamingResponseHeaders (headers …
diegosouzapw Jun 23, 2026
2cd5609
refactor(chatCore): extrai storeStreamingSemanticCacheResponse (cache…
diegosouzapw Jun 23, 2026
5bffefb
refactor(chatCore): extrai assembleStreamingPipeline (chain de transf…
diegosouzapw Jun 23, 2026
7f5b1e4
ci(quality): shift heavy validations to the PR→release fast-path (rel…
diegosouzapw Jun 23, 2026
c922b21
fix(quota): cota exclusiva lista qtSd/ no /v1/models (#4806) + limite…
diegosouzapw Jun 23, 2026
502f43c
feat(sse): add Google Flow video-generation provider (#4569) (#4769)
diegosouzapw Jun 23, 2026
fcf162d
fix(api): auth on compression run-telemetry + document OMNIROUTE_EVAL…
diegosouzapw Jun 23, 2026
fc2fdc7
fix(translator): strip top-level client_metadata on the OpenAI passth…
diegosouzapw Jun 24, 2026
ea406b2
fix(translator): normalize `developer` role to `system` for OpenAI-fo…
diegosouzapw Jun 24, 2026
2a90a9e
fix(translator): emit </think> close marker for Anthropic thinking bl…
diegosouzapw Jun 24, 2026
a9368af
fix(translator): normalize tools to Anthropic-native shape for non-An…
diegosouzapw Jun 24, 2026
35a3962
fix(gemini): preserve `pattern` in antigravity tool schema sanitizer …
diegosouzapw Jun 24, 2026
f4fa983
fix(perplexity): validate API keys via /v1/models endpoint (#4654)
diegosouzapw Jun 24, 2026
d2aa67a
fix(image): prevent compatible nodes from shadowing provider aliases …
diegosouzapw Jun 24, 2026
1ad6c46
fix(cli-tools): tolerate JSONC (comments, trailing commas) in tool se…
diegosouzapw Jun 24, 2026
983e152
fix(security): validate kiro region to prevent SSRF (GHSA-6mwv-4mrm-5…
diegosouzapw Jun 24, 2026
e176774
fix(cli): harden the systray2 tray runtime (port of 9router#1080) (#4…
diegosouzapw Jun 24, 2026
32b2df9
fix(test): validate anthropic-compatible connections via POST /v1/mes…
diegosouzapw Jun 24, 2026
607ad12
fix(executors): strip params unsupported by the target provider/model…
diegosouzapw Jun 24, 2026
be2ab9e
fix(claude-oauth): respect 429 backoff on usage endpoint to reduce sp…
diegosouzapw Jun 24, 2026
69703ee
feat(api/v1): include alias-backed models in /v1/models listing (#4630)
diegosouzapw Jun 24, 2026
306863d
chore(quality): rebaseline catalog.ts 1574->1577 (#4630 aliases sobre…
diegosouzapw Jun 24, 2026
ec1c5df
feat(compression): Kiro/CodeWhisperer tool-result compression engine …
diegosouzapw Jun 24, 2026
486f017
fix(security): don't trust loopback socket as local when behind rever…
diegosouzapw Jun 24, 2026
b92f045
fix(opencode): preserve DeepSeek reasoning content in streamed respon…
diegosouzapw Jun 24, 2026
57de29a
fix(copilot,antigravity): cap maxOutputTokens at 16384 to stop "Inval…
diegosouzapw Jun 24, 2026
c02aa7b
fix(dashboard): show custom vision models in LLM selector (#4653)
diegosouzapw Jun 24, 2026
6fe21e5
fix(claude): omit adaptive thinking + output_config.effort for haiku …
diegosouzapw Jun 24, 2026
2d09f72
feat(provider): CodeBuddy CN (copilot.tencent.com) — full stack (#4664)
diegosouzapw Jun 24, 2026
122f6d3
feat(combo): Fusion strategy — parallel panel + judge synthesis (16th…
diegosouzapw Jun 24, 2026
396a9a7
feat(proxy-pool): Deno Deploy relays + group action buttons (#4643)
diegosouzapw Jun 24, 2026
9244d93
fix(security): pin image fetch DNS resolution to prevent SSRF rebindi…
diegosouzapw Jun 24, 2026
cc8e1be
fix(github): route Copilot Codex models to /responses (port from 9rou…
diegosouzapw Jun 24, 2026
b599577
fix(copilot): never route Gemini/Claude variants to /responses (chat-…
diegosouzapw Jun 24, 2026
dad55fe
docs(ops): add canonical incident response runbook (#4868)
KooshaPari Jun 24, 2026
21aa561
docs(perf): add per-endpoint p50/p95/p99 latency + cost budgets (#4867)
KooshaPari Jun 24, 2026
9a5627a
fix(proxy): fan out direct dispatcher streams (#4803)
makcimbx Jun 24, 2026
805519b
fix(antigravity): exclude standard Gemini rate limit message from quo…
Chewji9875 Jun 24, 2026
e1ffc8e
fix(sse): skip third-party tool-name cloak for Anthropic server tools…
NomenAK Jun 24, 2026
604654c
fix(install): make transformers optional for CUDA-host installs (#4807)
megamen32 Jun 24, 2026
e99e288
fix(combo): propagate selected connection ID to fallback error respon…
Chewji9875 Jun 24, 2026
459ffcf
fix db storage tuning settings (#4834)
rdself Jun 24, 2026
108e93e
fix(sse): drop ccp pin when pinned provider is durably unhealthy (fai…
costaeder Jun 24, 2026
86b14ad
fix(claude): skip mcp__ tool-name cloak + guard missing connectionId …
costaeder Jun 24, 2026
4d61198
chore(quality): reconcile env-doc + file-size base-reds in release/v3…
diegosouzapw Jun 24, 2026
eb175db
fix(codex): drop non-standard codex.* events that break responses.str…
jeffer1312 Jun 24, 2026
2a7f2c8
feat(routing): honor X-Route-Model header to override body.model (#4863)
costaeder Jun 24, 2026
c522454
feat(live-ws): allow non-loopback clients via LIVE_WS_ALLOWED_HOSTS (…
KooshaPari Jun 24, 2026
2039095
chore(claude,codex): bump pinned CLI identity — Claude 2.1.158→2.1.18…
diegosouzapw Jun 24, 2026
5c0cda9
fix(security): SSRF allowlist bypass via x-relay-path nos relays Deno…
diegosouzapw Jun 24, 2026
27c994f
feat(quota): recuperação proativa de conexões em cooldown (cron heal)…
diegosouzapw Jun 24, 2026
4305ef8
fix(quota): policy inválida não vaza allow + guard connectionIds vazi…
diegosouzapw Jun 24, 2026
70afa2b
feat(quota): saturação real do Claude no fair-share via /api/oauth/us…
diegosouzapw Jun 24, 2026
f9ccf34
chore(dashboard): rename Qoder display label from "Qoder AI" to "Qode…
diegosouzapw Jun 24, 2026
9b07e0e
fix(ci): include coverage/lcov.info in coverage-report artifact for S…
diegosouzapw Jun 24, 2026
95e26a0
fix(cli): bump better-sqlite3 runtime pin to 12.10.1 for Node 26 (#4685)
diegosouzapw Jun 24, 2026
ec0572d
docs: clarify Kiro is ~50 credits/month per account, not unlimited (#…
diegosouzapw Jun 24, 2026
db192ce
docs(agentbridge): document Electron NODE_EXTRA_CA_CERTS, real model …
diegosouzapw Jun 24, 2026
413d1d3
docs(ops): document the release-green family (green-prs, check:releas…
diegosouzapw Jun 24, 2026
7a6f432
fix(translator): replay reasoning_content on plain Xiaomi MiMo turns …
diegosouzapw Jun 24, 2026
8251e7b
feat(opencode-go): advertise glm-5.2 and kimi-k2.7-code (align with o…
diegosouzapw Jun 24, 2026
1a1a51c
feat(db): track API endpoint dimension on usage_history (#4676)
diegosouzapw Jun 24, 2026
078785c
fix(cli): SIGKILL systray child PID before IPC close to avoid macOS N…
diegosouzapw Jun 24, 2026
8080800
feat(proxy-pool): Cloudflare Workers proxy deployer + pool integratio…
diegosouzapw Jun 24, 2026
5d9ecf6
chore(quality): conserta base-red de release/v3.8.36 (gates + 7 teste…
diegosouzapw Jun 24, 2026
9d59541
feat(quota): saturação proativa por headers de tokens (universal) [Fa…
diegosouzapw Jun 24, 2026
118875e
feat(quota): estratégia de combo "headroom" — seleção por folga de co…
diegosouzapw Jun 24, 2026
9829616
feat(quota): cap per-(key,model) — quota_allocation_model_caps [Fase …
diegosouzapw Jun 24, 2026
0811a15
feat(quota): session stickiness p/ integridade de prompt-cache [Fase …
diegosouzapw Jun 24, 2026
15ee83b
feat(quota): buckets multi-janela por conexão (5h/7d/per-model) [Fase…
diegosouzapw Jun 24, 2026
b8e3dc9
refactor(providers): decompõe catálogo providers.ts em módulos de dad…
diegosouzapw Jun 24, 2026
c872b67
refactor(pricing): decompõe pricing.ts em shared-tiers + DEFAULT_PRIC…
diegosouzapw Jun 24, 2026
21ce487
refactor(api): extrai camada-folha pura de validation.ts (URL/headers…
diegosouzapw Jun 24, 2026
1cb2462
refactor(api): extrai validators web-cookie + Meta AI de validation.t…
diegosouzapw Jun 24, 2026
6035fd8
refactor(api): extrai validators enterprise-cloud + probe compartilha…
diegosouzapw Jun 24, 2026
833adce
refactor(api): extrai validators áudio/speech + misc apikey de valida…
diegosouzapw Jun 24, 2026
9708288
feat(quota): estratégia dedicada de quota-share (DRR + P2C in-flight …
diegosouzapw Jun 24, 2026
537e5b5
refactor(api): extrai validators search + embedding/rerank de validat…
diegosouzapw Jun 24, 2026
419a90f
refactor(api): extrai format-validators (OpenAI/Anthropic) de validat…
diegosouzapw Jun 24, 2026
f158980
refactor(db): extrai model-permission matching de db/apiKeys.ts (#4936)
diegosouzapw Jun 24, 2026
ca0fddd
refactor(db): extrai row-parsers + tipos compartilhados de db/apiKeys…
diegosouzapw Jun 24, 2026
5073aff
refactor(db): extrai column-mapping (snake↔camel) de db/core.ts (#4947)
diegosouzapw Jun 24, 2026
ca72de7
refactor(db): extrai schema-column reconciliation de db/core.ts (#4948)
diegosouzapw Jun 24, 2026
565b771
refactor(sse): extrai scalar/format helpers de services/usage.ts (#4949)
diegosouzapw Jun 24, 2026
de19b3b
refactor(sse): extrai quota-core (UsageQuota + builders) de services/…
diegosouzapw Jun 24, 2026
b1b3069
fix(translator): regroup parallel tool results adjacent to their assi…
diegosouzapw Jun 24, 2026
cc8557c
fix(qoder): exchange PAT for jt-* job token before Cosy chat (#4683) …
diegosouzapw Jun 24, 2026
6e28889
refactor(sse): dedup fallback tool_call id helper (#4736)
diegosouzapw Jun 24, 2026
4a1ae86
refactor(open-sse): extract safeParseJSON util, dedup tryParseJSON (#…
diegosouzapw Jun 24, 2026
950fc6e
fix(compression): eliminate ReDoS in math_inline preservation pattern…
diegosouzapw Jun 24, 2026
7c75b48
fix(combo): fetch models dynamically from custom provider endpoints (…
diegosouzapw Jun 24, 2026
d65050a
feat(providers): update volcengine-ark model list with DeepSeek V4 (#…
diegosouzapw Jun 24, 2026
6137d4e
fix(translator): provider thinking compatibility (DeepSeek/Gemini) (#…
diegosouzapw Jun 24, 2026
07b385d
feat(combo): task-aware routing strategy (#4945)
diegosouzapw Jun 24, 2026
8a7cf50
refactor(sse): extrai a família MiniMax de services/usage.ts (#4952)
diegosouzapw Jun 24, 2026
cca71a5
refactor(sse): extrai a família GLM de services/usage.ts (#4953)
diegosouzapw Jun 24, 2026
d575691
refactor(sse): extrai a família Antigravity de services/usage.ts (#4956)
diegosouzapw Jun 24, 2026
3d37242
fix(dashboard): show custom provider given-name instead of internal i…
diegosouzapw Jun 24, 2026
d4c2e4d
fix(api): evict stale in-memory rate-limit windows to stop slow heap …
diegosouzapw Jun 24, 2026
cd27701
fix(api): parse /v1/responses body once instead of 3-4x on the hot pa…
diegosouzapw Jun 24, 2026
0720940
fix(translator): preserve legitimate empty-string tool arguments in o…
diegosouzapw Jun 24, 2026
678cbcf
chore(quality): reconcile file-size baseline for #4960 provider-displ…
diegosouzapw Jun 24, 2026
b03134e
fix(dashboard): restore home provider-topology card hidden by #4596 d…
diegosouzapw Jun 24, 2026
d3e02e6
fix(build): drop @omniroute/open-sse from optimizePackageImports (bui…
diegosouzapw Jun 24, 2026
a58183a
fix(quota): migração 107 ativa estratégia quota-share nos combos qtSd…
diegosouzapw Jun 24, 2026
195e40f
feat(quota): respeita max_concurrent por conexão no roteamento (#4965)
diegosouzapw Jun 24, 2026
a9ddef1
feat(quota): combo quota-share espera cooldown curto e re-despacha (V…
diegosouzapw Jun 24, 2026
028ed4e
fix(quality): resolve base-reds da release — db-rules allowlist + tas…
diegosouzapw Jun 25, 2026
1f685eb
feat(quota): serializa concorrência por conexão no caminho quota-shar…
diegosouzapw Jun 25, 2026
d648e21
docs(resilience): document Quota-Share Concurrency Control (max_concu…
diegosouzapw Jun 25, 2026
568472d
fix(dashboard): proxy-pool success gating, sync timestamp, opt-in Red…
diegosouzapw Jun 25, 2026
d38bf09
fix(sse): fail over on 400 responses carrying rate-limit text (#4976)…
diegosouzapw Jun 25, 2026
bf6c8ca
fix(compression): stop RTK over-truncating file-read tool results (#4…
diegosouzapw Jun 25, 2026
083d4b0
fix(sse): honor per-account proxies and fingerprint rotation in openc…
diegosouzapw Jun 25, 2026
0412b12
fix(sse): soft-penalize exhausted providers in auto-combo scoring (#4…
diegosouzapw Jun 25, 2026
af32e00
fix(dashboard): switch to visible filter after auto-hiding failed mod…
diegosouzapw Jun 25, 2026
917b224
fix(pollinations): only enable jsonMode when JSON output is requested…
diegosouzapw Jun 25, 2026
515bc34
fix(antigravity): default safetySettings to all-OFF for parity with n…
diegosouzapw Jun 25, 2026
09d0d11
fix(chatgpt-web): map advertised gpt-5.5/5.4-pro/5.2-pro slugs to pre…
diegosouzapw Jun 25, 2026
325188e
feat(combos): add editable per-combo description field persisted via …
diegosouzapw Jun 25, 2026
3eb9ff2
Fix Ollama Cloud max reasoning effort (#4993)
Thinkscape Jun 25, 2026
d40200a
fix(copilot): replace execSync with execFile to prevent command injec…
hamsa0x7 Jun 25, 2026
8c18955
fix(plugin): auth.json dual-key fallback for auto-prefix migration (#…
herjarsa Jun 25, 2026
50df2ca
feat(endpoint): per-endpoint custom system prompt injection (#5022)
diegosouzapw Jun 25, 2026
2d39c95
fix(headroom): translate openai-responses input through OpenAI for co…
diegosouzapw Jun 25, 2026
db9724d
docs(changelog): add entries for #4993, #5024, #5027 (release notes c…
diegosouzapw Jun 25, 2026
06882d6
fix(api): stop /api/system/env/repair 500 on packaged install (#5006)…
diegosouzapw Jun 25, 2026
182bc39
chore(release): finalize v3.8.36 CHANGELOG + docs (2026-06-25)
diegosouzapw Jun 25, 2026
c36ec10
fix(combos): PUT /api/combos/{id} rejects empty / no-op bodies with 400
KooshaPari Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
73 changes: 67 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ STORAGE_ENCRYPTION_KEY_VERSION=v1
DISABLE_SQLITE_AUTO_BACKUP=false

# ── Redis (Rate Limiting) ──
# Redis connection URL for the rate limiter backend.
# Redis connection URL for the rate limiter backend. OPT-IN: leave this
# commented out to use the built-in in-memory rate limiter. Setting it to a
# non-running localhost (#4878) makes ioredis flood "[REDIS] Error:" logs.
# Used by: src/shared/utils/rateLimiter.ts
# Default: redis://localhost:6379 (or redis://redis:6379 in Docker)
REDIS_URL=redis://localhost:6379
# Example: redis://localhost:6379 (or redis://redis:6379 in Docker)
# REDIS_URL=redis://localhost:6379

# ═══════════════════════════════════════════════════════════════════════════════
# 3. NETWORK & PORTS
Expand Down Expand Up @@ -551,6 +553,16 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# heuristic in instrumentation-node.ts. Default: unset (tests skip background).
#OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS=1

# Proactive connection-cooldown recovery (#8): re-validates connections whose
# transient `rate_limited_until` window has elapsed OUTSIDE the request hot path,
# so the first request after a cooldown does not pay the probe latency. Lazy
# recovery in getProviderCredentials still applies regardless. Used by:
# src/lib/quota/connectionRecovery.ts.
# Tick cadence (ms). Default 60000, floor 5000.
# OMNIROUTE_CONNECTION_RECOVERY_INTERVAL_MS=60000
# Disable the proactive recovery scheduler entirely (default: false).
# OMNIROUTE_DISABLE_CONNECTION_RECOVERY=false

# Background job interval for budget reset checks (ms). Default: 600000 (10m).
# Used by: src/lib/jobs/budgetResetJob.ts. Floor: 10000.
#OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS=600000
Expand Down Expand Up @@ -598,6 +610,11 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# Used by: scripts/postinstall.mjs.
#OMNIROUTE_SKIP_POSTINSTALL=0

# Operator-supplied JSON credentials for the offline compression-eval CLI
# (parsed with JSON.parse; leave unset for a dry run). Developer tooling only.
# Used by: scripts/compression-eval/index.ts. Default: {} (empty).
#OMNIROUTE_EVAL_CREDENTIALS={}

# Skip the DB healthcheck entirely on startup (useful for short-lived tasks / tests).
# Used by: src/lib/db/core.ts, src/lib/db/healthCheck.ts. Set to 1 to disable. Default: 0.
#OMNIROUTE_SKIP_DB_HEALTHCHECK=0
Expand Down Expand Up @@ -792,7 +809,7 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
# Used by: open-sse/executors/base.ts — buildHeaders() dynamic lookup.
# Update these when providers release new CLI versions to avoid blocks.

CLAUDE_USER_AGENT="claude-cli/2.1.158 (external, cli)"
CLAUDE_USER_AGENT="claude-cli/2.1.187 (external, cli)"

# Disable the deterministic tool-name cloak applied on both Anthropic-bound paths
# (executors/base.ts native OAuth + executors/cliproxyapi.ts CLIProxyAPI) —
Expand All @@ -801,7 +818,7 @@ CLAUDE_USER_AGENT="claude-cli/2.1.158 (external, cli)"
# stream with a misleading 400 out-of-extra-usage placeholder. Set to true to
# forward the original names verbatim (debugging only).
# CLAUDE_DISABLE_TOOL_NAME_CLOAK=false
CODEX_USER_AGENT="codex-cli/0.132.0 (Windows 10.0.26200; x64)"
CODEX_USER_AGENT="codex-cli/0.142.0 (Windows 10.0.26200; x64)"
GITHUB_USER_AGENT="GitHubCopilotChat/0.45.1"
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"
Expand All @@ -823,7 +840,13 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"

# 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.132.0
# CODEX_CLIENT_VERSION=0.142.0

# 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
# responses.stream() with a 502 "Controller is already closed". Off by default;
# set to true/1/yes to enable. Used by: open-sse/executors/codex.ts.
# OMNIROUTE_CODEX_DROP_NONSTANDARD_EVENTS=true

# ═══════════════════════════════════════════════════════════════════════════════
# 13. CLI FINGERPRINT COMPATIBILITY (Anti-Detection)
Expand Down Expand Up @@ -950,6 +973,15 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
# OMNIROUTE_CIRCUIT_BREAKER_LOCAL_THRESHOLD=2
# OMNIROUTE_CIRCUIT_BREAKER_LOCAL_RESET_MS=15000

# ── Context-cache pin health gate ──
# Used by: open-sse/services/combo.ts. When a context-cache pin points at a
# provider that is durably unhealthy, the pin is dropped to allow failover.
# PIN_DROP_BACKOFF_LEVEL gates how deep a connection's backoff must be before the
# pin is considered durably unhealthy; PIN_DROP_GRACE_MS is the anti-flap window
# that tolerates brief transient cooldowns before dropping the pin.
# PIN_DROP_BACKOFF_LEVEL=2
# PIN_DROP_GRACE_MS=20000

# ── Stream idle detection ──
# STREAM_IDLE_TIMEOUT_MS=600000 # Max silence between SSE chunks (default: 600000)
# # Extended-thinking models rarely pause >90s.
Expand Down Expand Up @@ -1200,6 +1232,34 @@ APP_LOG_TO_FILE=true
# Used by: open-sse/executors/cloudflare-ai.ts
# CLOUDFLARE_ACCOUNT_ID=

# ── Deno Deploy proxy relay (#4643 / 9router#1437) ──
# Override the Deno Deploy REST API base used by the proxy-pool relay deployer.
# Default: https://api.deno.com/v2 (omit unless mocking).
# Used by: src/app/api/settings/proxy/deno-deploy/route.ts
# DENO_DEPLOY_API_BASE=https://api.deno.com/v2

# Default Deno Deploy app name suggested in the "Deploy Relay" modal.
# Used by: src/app/(dashboard)/dashboard/settings/components/proxy/DenoRelayModal.tsx
# NEXT_PUBLIC_DENO_RELAY_DEFAULT_PROJECT=omniroute-deno-relay

# Set to "false" to hide the Deno Deploy relay option from the Proxy Pool tab.
# Used by: src/app/(dashboard)/dashboard/settings/components/proxy/ProxyPoolTab.tsx
# NEXT_PUBLIC_DENO_RELAY_ENABLED=true

# ── Cloudflare Workers proxy relay (#4640 / 9router#1360) ──
# Override the Cloudflare REST API base used by the proxy-pool relay deployer.
# Default: https://api.cloudflare.com/client/v4 (omit unless mocking).
# Used by: src/app/api/settings/proxy/cloudflare-deploy/route.ts
# CLOUDFLARE_API_BASE=https://api.cloudflare.com/client/v4

# Default worker project name suggested in the "Deploy Relay" modal.
# Used by: src/app/(dashboard)/dashboard/settings/components/proxy/CloudflareRelayModal.tsx
# NEXT_PUBLIC_CLOUDFLARE_RELAY_DEFAULT_PROJECT=omniroute-relay

# Set to "false" to hide the Cloudflare Workers relay option from the Proxy Pool tab.
# Used by: src/app/(dashboard)/dashboard/settings/components/proxy/ProxyPoolTab.tsx
# NEXT_PUBLIC_CLOUDFLARE_RELAY_ENABLED=true

# ── Cloudflare Tunnel (cloudflared) ──
# Custom path to cloudflared binary for tunnel management.
# Used by: src/lib/cloudflaredTunnel.ts
Expand Down Expand Up @@ -1637,6 +1697,7 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
# QUOTA_STORE_REDIS_URL= # ex.: redis://localhost:6379 (apenas quando driver=redis)
# QUOTA_SATURATION_THRESHOLD=0.5 # 0..1; >= threshold ativa modo strict (sem empréstimo)
# QUOTA_SOFT_DEPRIORITIZE_FACTOR=0.7 # 0..1; multiplicador do score quando soft policy ativa
# STATUS_SOFT_DEPRIORITIZE_FACTOR=0.5 # 0..1; multiplicador do score p/ provider esgotado (credits_exhausted/rate_limited) quando preflight cutoff OFF (#4540)
# QUOTA_CONSUMPTION_RETENTION_DAYS=14 # GC de buckets quota_consumption.updated_at antigos
# QUOTA_PREFLIGHT_CUTOFF_ENABLED=false # opt-in (default OFF): hard quota cutoff drops low-quota candidates before auto-routing scoring

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ jobs:
path: |
coverage/coverage-summary.json
coverage/coverage-report.md
coverage/lcov.info
if-no-files-found: warn

sonarqube:
Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
- run: npm run check:fetch-targets
- run: npm run check:openapi-routes
- run: npm run check:docs-symbols
- name: Docs accuracy (fabricated-docs + i18n mirrors, strict)
run: npm run check:docs-all
- run: npm run check:deps
- run: npm run check:file-size
- run: npm run check:error-helper
Expand All @@ -49,6 +51,7 @@ jobs:
- run: npm run check:known-symbols
- run: npm run check:route-guard-membership
- run: npm run check:test-discovery
- run: npm run check:test-runner-api
- run: npm run check:any-budget:t11
- name: Typecheck (core)
run: npm run typecheck:core
Expand Down Expand Up @@ -82,3 +85,48 @@ jobs:
echo "Running impacted tests:"; echo "$SEL"
mapfile -t FILES <<< "$SEL"
node --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 "${FILES[@]}"

fast-vitest:
name: Vitest (fast-path)
runs-on: ubuntu-latest
env:
JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation
API_KEY_SECRET: ci-lint-api-key-secret-long
DISABLE_SQLITE_AUTO_BACKUP: "true"
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: ${{ env.CI_NODE_VERSION }}
cache: npm
- run: npm ci
- run: npm run test:vitest

fast-unit:
name: Unit Tests fast-path (${{ matrix.shard }}/2)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2]
env:
JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation
API_KEY_SECRET: ci-lint-api-key-secret-long
DISABLE_SQLITE_AUTO_BACKUP: "true"
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: ${{ env.CI_NODE_VERSION }}
cache: npm
- run: npm ci
- run: >
node --max-old-space-size=4096 --import tsx
--import ./tests/_setup/isolateDataDir.ts
--test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/2
tests/unit/*.test.ts
"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,dashboard,db,db-adapters,docs,gamification,guardrails,lib,mcp,runtime,security,services,settings,shared,ui}/**/*.test.ts"
20 changes: 18 additions & 2 deletions @omniroute/opencode-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4406,8 +4406,24 @@ export function createOmniRouteConfigHook(
authJson = undefined;
}

const entry = authJson?.[resolved.providerId] as AuthJsonApiEntry | undefined;
const apiKey = entry && entry.type === "api" && typeof entry.key === "string" ? entry.key : "";
// Try both prefixed (e.g. opencode-omniroute) and unprefixed (e.g. omniroute)
// keys so a user who ran `/connect omniroute` before the auto-prefix fix
// does not need to re-auth. Also handles dual-key for auth.json entries
// written by a newer OC dispatcher with the prefixed key.
const bareKey = resolved.providerId.startsWith("opencode-")
? resolved.providerId.slice("opencode-".length)
: resolved.providerId;
const lookupKeys = [resolved.providerId];
if (bareKey !== resolved.providerId) lookupKeys.push(bareKey);
let entry;
for (const k of lookupKeys) {
const e = authJson?.[k];
if (e?.type === "api" && typeof e.key === "string" && e.key.length > 0) {
entry = e;
break;
}
}
const apiKey = entry?.type === "api" && typeof entry.key === "string" ? entry.key : "";

if (!apiKey) {
// (c) no apiKey — silent no-op (with debug breadcrumb). The operator
Expand Down
59 changes: 59 additions & 0 deletions @omniroute/opencode-plugin/tests/config-shim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,65 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
assert.equal(combo.limit?.context, 200_000, "LCD: min(200_000, 1_000_000)");
});

// ────────────────────────────────────────────────────────────────────────────
// 1b. Dual-key fallback (#5027) — auth.json stored under the BARE providerId
// (pre-auto-prefix login) must still resolve when the active providerId is
// prefixed (`opencode-omniroute`). Without the fallback the lookup misses
// the stored key and the user is forced to re-auth.
// ────────────────────────────────────────────────────────────────────────────

test("config: auth.json under bare key (pre-prefix login) resolves via dual-key fallback", async () => {
// Stored under bare `omniroute` (the key OC wrote before the auto-prefix fix),
// but the resolved providerId is now `opencode-omniroute`.
const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-bare-1", baseURL: "https://or.example.com/v1" },
});
const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]);
const logger = captureWarn();

const hook = createOmniRouteConfigHook(
{ providerId: "omniroute" }, // resolves to opencode-omniroute internally
{ readAuthJson, fetcher, combosFetcher, logger }
);
const input = makeInput();
await hook(input);

const provider = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider;
const entry = provider["opencode-omniroute"];
assert.ok(entry, "provider entry published from bare-key apiKey");
assert.equal(entry.options.apiKey, "sk-bare-1", "apiKey resolved from the bare auth.json key");
assert.equal(entry.options.baseURL, "https://or.example.com/v1");
});

test("config: prefixed key wins over bare key when both present (dual-key precedence)", async () => {
const readAuthJson = stubReadAuthJson({
"opencode-omniroute": { type: "api", key: "sk-prefixed", baseURL: "https://pref.example/v1" },
omniroute: { type: "api", key: "sk-bare", baseURL: "https://bare.example/v1" },
});
const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]);
const logger = captureWarn();

const hook = createOmniRouteConfigHook(
{ providerId: "omniroute" },
{ readAuthJson, fetcher, combosFetcher, logger }
);
const input = makeInput();
await hook(input);

const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
"opencode-omniroute"
];
assert.ok(entry);
assert.equal(
entry.options.apiKey,
"sk-prefixed",
"prefixed key takes precedence (looked up first)"
);
assert.equal(entry.options.baseURL, "https://pref.example/v1");
});

// ────────────────────────────────────────────────────────────────────────────
// 2. Missing auth.json → no-op, no throw, no mutation
// ────────────────────────────────────────────────────────────────────────────
Expand Down
Loading
Loading