From e42e55426ec18d111b7e31a3f5bd8cd6dda140ad Mon Sep 17 00:00:00 2001 From: Devin Date: Sat, 4 Jul 2026 10:57:35 +0800 Subject: [PATCH] i18n(zh-CN): translate CHANGELOG entries and section headings (#6043) Adopt zh-CN as a translated locale: translate CHANGELOG + supporting docs. Integrated into release/v3.8.44. --- docs/i18n/zh-CN/CHANGELOG.md | 11126 ++++++++-------- docs/i18n/zh-CN/CLAUDE.md | 541 +- docs/i18n/zh-CN/CODE_OF_CONDUCT.md | 135 +- docs/i18n/zh-CN/CONTRIBUTING.md | 410 +- docs/i18n/zh-CN/GEMINI.md | 57 +- docs/i18n/zh-CN/README.md | 717 +- docs/i18n/zh-CN/SECURITY.md | 253 +- .../zh-CN/docs/architecture/ARCHITECTURE.md | 1172 +- .../architecture/CODEBASE_DOCUMENTATION.md | 1263 +- .../zh-CN/docs/cloudflare-zero-trust-guide.md | 130 +- .../i18n/zh-CN/docs/features/context-relay.md | 128 +- docs/i18n/zh-CN/docs/frameworks/A2A-SERVER.md | 157 +- docs/i18n/zh-CN/docs/frameworks/MCP-SERVER.md | 432 +- docs/i18n/zh-CN/docs/guides/FEATURES.md | 303 +- docs/i18n/zh-CN/docs/guides/I18N.md | 585 +- .../i18n/zh-CN/docs/guides/TROUBLESHOOTING.md | 511 +- docs/i18n/zh-CN/docs/guides/UNINSTALL.md | 132 +- docs/i18n/zh-CN/docs/guides/USER_GUIDE.md | 951 +- docs/i18n/zh-CN/docs/ops/COVERAGE_PLAN.md | 201 +- .../zh-CN/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md | 70 +- docs/i18n/zh-CN/docs/ops/RELEASE_CHECKLIST.md | 340 +- .../zh-CN/docs/ops/VM_DEPLOYMENT_GUIDE.md | 249 +- .../zh-CN/docs/reference/API_REFERENCE.md | 1286 +- docs/i18n/zh-CN/docs/reference/CLI-TOOLS.md | 728 +- docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md | 1280 +- docs/i18n/zh-CN/docs/routing/AUTO-COMBO.md | 606 +- scripts/check/check-docs-sync.mjs | 11 +- 27 files changed, 14004 insertions(+), 9770 deletions(-) diff --git a/docs/i18n/zh-CN/CHANGELOG.md b/docs/i18n/zh-CN/CHANGELOG.md index c12a4802f3..917c3e2c4d 100644 --- a/docs/i18n/zh-CN/CHANGELOG.md +++ b/docs/i18n/zh-CN/CHANGELOG.md @@ -10,473 +10,491 @@ ### ✨ New Features -_TBD_ +- **feat(api):** add `/v1/ocr` endpoint (Mistral OCR), an OCR provider category, and Mistral moderation support. (thanks @waguriagentic) +- **Discovery tool (Phase 2):** add the `discoveryResults` DB module (CRUD over the `discovery_results` table, migration 074) and wire the opt-in provider-discovery service to persist and read findings through it (`persistDiscoveryResult`, `getDiscoveryResults`, `getDiscoveryResultById`, `markVerified`, `deleteDiscoveryResult`) with `(provider, method, endpoint)` upsert de-duplication. Adds the `/api/discovery/*` HTTP surface — `GET /results`, `GET|DELETE /results/:id`, `POST /scan`, `POST /verify/:id` — under **strict loopback-only** authorization (`/api/discovery/` is in `LOCAL_ONLY_API_PREFIXES` and is NOT manage-scope-bypassable, so the `scan` route's outbound probes can never be reached from a tunnel/remote origin). Adds a **dashboard UI tab** (Tools → Discovery, `/dashboard/discovery`) to run scans and review, verify, or delete findings. The service stays **opt-in / default-off**. +- **feat(api):** expose a read-only provider plugin manifest at `GET /api/v1/provider-plugin-manifest` for sidecar/relay discovery. (thanks @KooshaPari) +- **feat(sidecar):** advertise the provider manifest URL to Bifrost/CLIProxyAPI via the `X-OmniRoute-Provider-Manifest-Url` header (`OMNIROUTE_PROVIDER_MANIFEST_URL`). (thanks @KooshaPari) +- **feat(autoCombo):** add a latency/speed-optimized routing mode (shared `rankBySpeed` scoring core) plus the `omniroute_pick_fastest_model` MCP tool. (thanks @KooshaPari) +- **feat(providers):** refresh The Old LLM (Free) model catalog ([#5181](https://github.com/diegosouzapw/OmniRoute/issues/5181)) — seed the current free `/api/chatgpt` tier (GPT-5/5.1/5.2/5.3/5.4, o3/o4-mini, Gemini 3 Pro / 2.5 Pro / 2.0 Flash / 1.5 Flash, Claude 4.6 Opus/Sonnet & 4.5 Haiku, GPT-4o, Grok 4, DeepSeek V3/R1, Sonar Pro) while keeping the legacy alias IDs for saved-preference compatibility. Also fixes a latent routing bug: `mapModel()` now passes known upstream IDs through unchanged, so Gemini/o-series/Grok/DeepSeek/Sonar models no longer silently collapse onto `GPT_5_4`. Regression guard: `tests/unit/theoldllm-model-refresh-5181.test.ts`. (thanks @WslzGmzs) +- **feat(resilience):** surface Codex **banked reset credits** per connected account ([#5199](https://github.com/diegosouzapw/OmniRoute/issues/5199)) — the Codex quota parsers (`buildCodexUsageQuotas`, `parseCodexUsageResponse`) now additively read `rate_limit_reset_credits.available_count` (+ optional `rate_limit_reached_type`) from the `/wham/usage` payload OmniRoute already fetches, and the provider-limits dashboard renders a **"Banked Reset Credits"** row when a positive count is present. Display-only and **fail-open** — the field is eligibility-gated, so accounts without it are unaffected (parsers never throw on absent/garbage shapes); redemption (an unofficial mutating endpoint) is intentionally out of scope. Regression guard: `tests/unit/codex-banked-reset-credits-5199.test.ts` (8). (thanks @ofekbetzalel) +- **feat(providers):** add sign-up geo-restriction notices for **SenseNova** and **StepFun** ([#5462](https://github.com/diegosouzapw/OmniRoute/issues/5462)) — the provider add-form now warns that SenseNova's console appears to require a Chinese (+86) phone number with no documented international path, and that StepFun's default endpoint is its China platform while a global StepFun Open Platform (`platform.stepfun.ai`, operated by Sparkling AI Pte. Ltd., Singapore) with email/Google/Discord login exists for international users. Informational `notice` only — neither provider is disabled. Regression guard: `tests/unit/regional-provider-cn-notices-5462.test.ts`. (thanks @chirag127) +- **feat(usage):** add on-demand period-scoped usage-data reset (Settings → System Storage) with a purge API and time-window selector. +- **feat(claude-code):** add an opt-in auto-permission classifier compat mode (off/auto/always) for Claude Code, toggleable from the CLI Code settings. +- **feat(providers):** add optional client-identity header profiles for compatible nodes — preset User-Agent/fingerprint headers (e.g. matching a known CLI) merged into the existing customHeaders field. ### 🔧 Bug Fixes -_TBD_ +- **combo (prefer known context capacity over unknown):** when a combo filters out at least one target for exceeding a *known* context limit, the router now prefers the remaining known-compatible targets over targets whose context metadata is simply unknown, instead of letting unknown-metadata targets be the only survivors. If no known-compatible context target remains, context-only candidates fall back to the normal strategy order. Regression guard: `tests/unit/combo-context-window-filter.test.ts`. ([#6088](https://github.com/diegosouzapw/OmniRoute/pull/6088) — thanks @Thinkscape) + +- **models (GLM-5.2 context normalization):** stop treating every hosted GLM-5.2 provider alias as the native 1M-context model. Native/bare GLM-5.2 and verified OpenCode / ZenMux routes keep their 1,000,000-token context, while hosted-provider aliases now respect the caps declared in their provider metadata instead of inheriting the native max. Regression guards: `tests/unit/model-capabilities-registry.test.ts`, `tests/unit/models-catalog-route.test.ts`. ([#6091](https://github.com/diegosouzapw/OmniRoute/pull/6091) — thanks @Thinkscape) + +- **providers (Gemini Web):** refresh the Gemini Web cookie handling and model catalog so live Gemini Web sessions keep authenticating and routing to current models. Regression guard: `tests/unit/gemini-web.test.ts`. ([#6095](https://github.com/diegosouzapw/OmniRoute/pull/6095) — thanks @backryun) + +- **providers (Perplexity Web):** refresh the Perplexity Web model catalog to the current set (GPT-5.4/5.5, Claude Sonnet 5.0 / Opus 4.8, GLM-5.2, Kimi K2.6, Nemotron 3 Ultra) and update the internal mode / `model_preference` mappings and thinking variants so requests resolve to live upstream models. Regression guard: `tests/unit/perplexity-web.test.ts`. ([#6106](https://github.com/diegosouzapw/OmniRoute/pull/6106) — thanks @backryun) + +- **dashboard ("Update now" → Internal Server Error):** clicking **Update now** on the dashboard home could crash the page with a blank "Internal Server Error" screen (`Minified React error #31`). The handler POSTs the loopback-only `/api/system/version` auto-update endpoint and, on a non-OK JSON response (e.g. a `403` when the dashboard is reached through a reverse proxy / non-loopback origin), passed the raw error envelope object `{ error: { code, message, correlation_id } }` straight to `notify.error()`, which rendered the object as a React child and threw #31. The update-error path now funnels the body through `extractApiErrorMessage()` (the same safe extractor added in #5340), so a readable string always reaches the toast. Regression guard: `tests/unit/ui/home-update-error-render-5991.test.ts`. ([#5991](https://github.com/diegosouzapw/OmniRoute/issues/5991)) ### 📝 Maintenance -- **chore(release):** release-pipeline hardening — `check:test-masking` (vs `origin/main`) is now a HARD gate in the release-green pre-flight (`validate-release-green.mjs`), so a non-allowlisted net-assert reduction surfaces locally instead of in a ~40-min CI layer on the release PR; plus two reconciliation helpers — `npm run release:contributors` (reproducible `### 🙌 Contributors` table via a parenthetical-group parser) and `npm run release:uncovered` (lists commits with no CHANGELOG bullet). ([#5926](https://github.com/diegosouzapw/OmniRoute/pull/5926) — thanks @diegosouzapw) +- **test (deflake `setup-claude`):** `tests/unit/cli/setup-claude.test.ts` failed ~50% of runs with `Unable to deserialize cloned data due to invalid or unsupported version` at file teardown (all subtests passed), randomly reddening `Unit Tests fast-path (2/2)` / `Fast Quality Gates` across the PR→release queue. Root cause: `node --test` streams each file's report to the parent as V8-serialized frames on fd 1 (stdout), and the CLI helper under test (`syncClaudeProfilesFromModels`) prints progress via `console.log` — that stdout output interleaved with the serialized frames and corrupted the stream. The test now silences the stdout-writing `console` methods for the file's duration (no assertion inspects stdout), making it deterministic (15/15 green locally). ([#5959](https://github.com/diegosouzapw/OmniRoute/issues/5959)) -- **chore(ci):** the `check:pr-evidence` FAIL report now tells you that editing the PR body does not re-run the gate (`ci.yml` ignores the `edited` event) — push a commit to re-validate. ([#5944](https://github.com/diegosouzapw/OmniRoute/pull/5944) — thanks @diegosouzapw) +- **API validation:** add a `validatedJsonBody(request, schema)` helper in `src/shared/validation/helpers.ts` that fuses JSON body parsing and Zod validation into a single call, returning either the type-narrowed data or a ready-to-return 400 `NextResponse` with the standard error envelope. Salvaged from the closed refactor PR #5075 (Tier 1 portable helper) with a focused 6-case regression test. Co-authored-by: KooshaPari --- ## [3.8.43] — 2026-07-02 -### ✨ New Features +### ✨ 新功能 -- **usage (quota percentages + provider USD drilldown):** `@@om-usage` and the HTTP usage endpoint now report personal API-key quotas as **remaining percentages** (USD amounts stay out of the command output), provider quota remaining is scaled by the configured quota cutoff so the protected reserve reads as 0% left, and the quota dashboard regains a **provider USD cost drilldown** (`/api/usage/provider-window-costs` + `ProviderUsdCostModal`, management-auth gated). Also honors **observed provider quota resets**: a same-`resetAt` reset (usage dropping back to the reset floor) is detected and preferred over stale recorded weekly events for provider USD windows and API-key USD quotas. New `src/lib/usage/providerWindowCosts.ts`. Regression guards: `tests/unit/provider-window-costs.test.ts`, `tests/unit/internal-usage-command.test.ts`, `tests/unit/api-key-usage-limits.test.ts`, `tests/unit/lib/quota-reset-events.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4). +- **usage(配额百分比 + 服务商 USD 成本明细):** `@@om-usage` 和 HTTP 用量端点现在以**剩余百分比**的形式报告个人 API 密钥配额(USD 金额不出现在命令输出中),服务商配额剩余量会根据配置的配额截断值进行缩放,使受保护预留部分显示为剩余 0%,配额仪表盘恢复了**服务商 USD 成本明细**功能(`/api/usage/provider-window-costs` + `ProviderUsdCostModal`,需管理认证)。同时支持**观测到的服务商配额重置**:检测到相同 `resetAt` 的重置事件(用量回落到重置基准线)时,优先采用此观测值而非陈旧的每周记录事件,用于服务商 USD 窗口和 API 密钥 USD 配额。新增 `src/lib/usage/providerWindowCosts.ts`。回归测试:`tests/unit/provider-window-costs.test.ts`、`tests/unit/internal-usage-command.test.ts`、`tests/unit/api-key-usage-limits.test.ts`、`tests/unit/lib/quota-reset-events.test.ts`。提取自 [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863),由 [@Witroch4](https://github.com/Witroch4) 贡献。 -- **dashboard (live WS behind reverse proxy):** the live dashboard WebSocket can now be fronted by a reverse proxy or Cloudflare Tunnel via `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` (e.g. `wss://ws.my-ai.com/live-ws`). The URL is honored both at build time (env inlined into the bundle) and at **runtime** for prebuilt Docker/npm images: the `/api/v1/ws?handshake=1` handshake now echoes a lazily-read `live.publicUrl` (only `ws://`/`wss://` values are accepted; anything else is rejected to `null`), and `useLiveDashboard` resolves the URL from that handshake before connecting, falling back to the previous `ws(s)://hostname:20129` default. Also documents `LIVE_WS_ALLOWED_HOSTS` and aligns the GitLab Duo OAuth scopes line in `.env.example` with the live config (`ai_features read_user`). Regression guard: `tests/unit/live-ws-public-url.test.ts` (5). ([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877) by [@ianriizky](https://github.com/ianriizky)) +- **dashboard(反向代理后的实时 WebSocket):** 实时仪表盘 WebSocket 现在可以通过 `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` 被反向代理或 Cloudflare Tunnel 前置(例如 `wss://ws.my-ai.com/live-ws`)。该 URL 在构建时(环境变量内联到打包产物)和**运行时**(对于预构建的 Docker/npm 镜像)均生效:`/api/v1/ws?handshake=1` 握手现在会回显一个延迟读取的 `live.publicUrl`(仅接受 `ws://`/`wss://` 值,其他值被拒绝为 `null`),`useLiveDashboard` 在连接前从该握手解析 URL,回退到之前的 `ws(s)://hostname:20129` 默认值。同时文档化了 `LIVE_WS_ALLOWED_HOSTS`,并将 `.env.example` 中的 GitLab Duo OAuth 权限范围行与实时配置保持一致(`ai_features read_user`)。回归测试:`tests/unit/live-ws-public-url.test.ts`(5 个)。([#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877),由 [@ianriizky](https://github.com/ianriizky) 贡献) -- **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles//settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the CLI Code dashboard toggles each (moved from the providers dashboard in [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — thanks [@rdself](https://github.com/rdself)). Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737). +- **providers(CLI 配置文件自动同步):** 可选的开关,用于在服务商模型同步后自动重新生成 CLI 工具配置文件。启用后,模型目录的变更会从实时目录中(重新)写入该工具的配置文件 — Codex(`~/.codex/*.config.toml`)以及现在的 **Claude Code**(`~/.claude/profiles//settings.json`,通过提取的 `syncClaudeProfilesFromModels` + 新增的 `claudeProfileAutoSync.ts`,镜像 Codex 路径)。两者**默认关闭**,且绝不会触碰活动/默认的 CLI 配置;它们由 `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` 功能标志控制(DB/仪表盘覆盖 > 环境变量 > 默认 "false"),并额外受到现有 `CLI_ALLOW_CONFIG_WRITES` 写入保护的限制。CLI Code 仪表盘上新增**"CLI 配置文件自动同步"**卡片,可分别切换每个选项(从服务商仪表盘移至 [#5778](https://github.com/diegosouzapw/OmniRoute/pull/5778) — 感谢 [@rdself](https://github.com/rdself))。回归测试:`tests/unit/claude-profile-auto-sync-gate.test.ts`、`tests/unit/codex-profile-auto-sync-gate.test.ts`、`tests/unit/cli/setup-claude.test.ts`(#5737 的后续工作)。 -- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)). +- **cli(启动横幅):** `serve` 启动横幅现在在 ASCII 标志下方打印正在运行的 OmniRoute 版本(`v3.8.x`),使活动版本一目了然,无需单独的 `--version` 调用。回归测试:`tests/unit/cli-serve-version-banner.test.ts`。感谢 [@chirag127](https://github.com/chirag127)([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752))。 -- **analytics (subscription cost):** flat-rate providers now show **$0** in cost analytics instead of an inflated per-token estimate. Subscription / coding-plan providers (every cookie-web provider — ChatGPT Web, grok-web, … — plus the dedicated **Minimax Coding**, **Kimi Coding**, **GLM Coding**, **Alibaba Coding Plan**, and **Xiaomi MiMo** plans) bill a flat fee, not per token, yet still carry per-token pricing rows used for estimates — so the analytics dashboard over-reported their cost. A new flat-rate classifier (`src/lib/usage/flatRateProviders.ts`) is consulted by the analytics surfaces (analytics route, usage stats, usage analytics) via an opt-in `flatRateAsZero` cost option, so those providers read $0 while **budget / quota / routing keep estimating unchanged**. Deliberately NOT zeroed: `codex`/`cx` (OmniRoute actively tracks Codex token cost — Fast-tier multipliers, GPT-5.x pricing — and Codex can be a metered account), `byteplus` (metered ModelArk), `minimax-cn` (metered China API). Regression guard: `tests/unit/flat-rate-cost-5552.test.ts`. ([#5552](https://github.com/diegosouzapw/OmniRoute/issues/5552)) +- **analytics(订阅费用):** 固定费率服务商现在在成本分析中显示 **$0**,而非虚高的按 token 估算值。订阅/编程计划服务商(所有 cookie-web 服务商 — ChatGPT Web、grok-web 等 — 以及专用的 **Minimax Coding**、**Kimi Coding**、**GLM Coding**、**Alibaba Coding Plan**、**Xiaomi MiMo** 计划)按固定费用计费而非按 token 计费,但仍然携带用于估算的按 token 定价行 — 因此分析仪表盘之前会高估其成本。新增的固定费率分类器(`src/lib/usage/flatRateProviders.ts`)通过可选的 `flatRateAsZero` 成本选项被分析界面(分析路由、用量统计、用量分析)查询,使这些服务商显示 $0,同时**预算/配额/路由的估算保持不变**。刻意不归零的:`codex`/`cx`(OmniRoute 主动跟踪 Codex token 成本 — Fast 层级乘数、GPT-5.x 定价 — 且 Codex 可能是按量计费账户)、`byteplus`(按量计费的 ModelArk)、`minimax-cn`(按量计费的中国 API)。回归测试:`tests/unit/flat-rate-cost-5552.test.ts`。([#5552](https://github.com/diegosouzapw/OmniRoute/issues/5552)) -- **mcp (RTK):** expose the RTK tool-output **learn/discover** workflow as two new MCP tools so an agent can grow the RTK filter catalog without leaving the protocol. `omniroute_rtk_discover` analyzes recently captured raw tool output (`discoverRepeatedNoise` / `suggestFilter`) and returns candidate noise patterns plus a suggested filter; `omniroute_rtk_learn` lists the captured command samples (`listRtkCommandSamples`) and resolves a command to its RTK filter id (`commandToId`). Both are read-only (scope `read:compression`), wrap the existing RTK discovery primitives (no new logic in the engine), and log to the MCP audit trail. Regression guard: `tests/unit/compression/rtk-mcp-tools.test.ts` (4). gaps v3.8.42 — T07. +- **mcp(RTK):** 将 RTK 工具输出**学习/发现**工作流暴露为两个新的 MCP 工具,使代理无需离开协议即可扩展 RTK 过滤器目录。`omniroute_rtk_discover` 分析最近捕获的原始工具输出(`discoverRepeatedNoise` / `suggestFilter`)并返回候选噪声模式和建议的过滤器;`omniroute_rtk_learn` 列出捕获的命令样本(`listRtkCommandSamples`)并将命令解析为其 RTK 过滤器 ID(`commandToId`)。两者均为只读(作用域 `read:compression`),封装现有的 RTK 发现原语(引擎中无新逻辑),并记录到 MCP 审计跟踪。回归测试:`tests/unit/compression/rtk-mcp-tools.test.ts`(4 个)。v3.8.42 差距 — T07。 -- **compression (LLM tier):** add an **opt-in, default-off LLM-tier compression engine** (`llm`) that condenses the prose of non-system messages via a pluggable chat-completion backend. It mirrors the `llmlingua` engine's contract but is **safe by construction**: the default backend is a **no-op pass-through** (the engine never mutates the payload until an operator both enables it _and_ wires a real backend via `setLlmCompressorBackend()`), it is **not** part of the default stacked pipeline, `enabled` defaults to `false`, fenced code blocks and `system` messages are never sent to the model, and every backend error **fails open** (the original segment/body is kept, never thrown). A `minTokens` floor skips small prompts. The real production backend is intentionally a VPS-validated follow-up (Hard Rule #18), exactly as the `llmlingua` worker backend is gated. New `open-sse/services/compression/engines/llm/index.ts`. Regression guard: `tests/unit/compression/llm-compressor-engine.test.ts` (8). gaps v3.8.42 — T05/C3. +- **compression(LLM 层级):** 新增**可选、默认关闭的 LLM 层级压缩引擎**(`llm`),通过可插拔的聊天补全后端压缩非系统消息的文本。它镜像 `llmlingua` 引擎的契约,但**构造上更安全**:默认后端是**无操作透传**(在操作者同时启用它并通过 `setLlmCompressorBackend()` 连接真实后端之前,引擎不会改变有效载荷),它**不**属于默认堆叠管道的一部分,`enabled` 默认为 `false`,代码块和 `system` 消息绝不会发送给模型,每次后端错误都**安全开放**(原始片段/正文被保留,绝不丢弃)。`minTokens` 阈值跳过小型提示。真实生产后端有意作为 VPS 验证的后续工作(Hard Rule #18),与 `llmlingua` 工作后端受限的方式完全相同。新增 `open-sse/services/compression/engines/llm/index.ts`。回归测试:`tests/unit/compression/llm-compressor-engine.test.ts`(8 个)。v3.8.42 差距 — T05/C3。 -- **memory (typed decay):** add **opt-in typed memory decay** (TV6) so the conversational memory store stops accumulating stale `episodic` noise. Each injected memory now tracks an `access_count` + `last_accessed_at` (always-on, non-destructive telemetry; migration `111_memory_typed_decay`), and an **opt-in, default-off** sweep (`MEMORY_TYPED_DECAY_ENABLED`, default `false`) deletes memories that are past a **per-type TTL** and not immune. Only `episodic` decays by default (30d, env-tunable); `factual`/`procedural`/`semantic` are immune, and any memory accessed `>= 3` times earns access immunity (mirroring "guardrail/convention/decision never decay"). The decay clock re-bases on the last access, so used memories survive. Deletions reuse `deleteMemory` (SQLite + sqlite-vec + Qdrant stay in sync) and fail open; an optional periodic sweep is doubly opt-in (also needs `MEMORY_TYPED_DECAY_SWEEP_INTERVAL>0`). With the flag off nothing is ever deleted (Rule #20 spirit). New `src/lib/memory/typedDecay.ts`. Regression guard: `tests/unit/memory/typed-decay.test.ts` (15). gaps v3.8.42 — T10/TV6. +- **memory(类型化衰减):** 新增**可选类型化记忆衰减**(TV6),使对话记忆存储不再累积陈旧的 `episodic` 噪声。每条注入的记忆现在跟踪 `access_count` + `last_accessed_at`(始终开启、非破坏性遥测;迁移 `111_memory_typed_decay`),以及**可选、默认关闭**的清理(`MEMORY_TYPED_DECAY_ENABLED`,默认 `false`)会删除超过**每类型 TTL** 且未被豁免的记忆。只有 `episodic` 默认衰减(30 天,可通过环境变量调整);`factual`/`procedural`/`semantic` 被豁免,任何访问次数 `>= 3` 的记忆获得访问豁免(镜像"护栏/惯例/决策永不衰减")。衰减时钟基于最后一次访问重置,因此被使用的记忆会留存。删除复用 `deleteMemory`(SQLite + sqlite-vec + Qdrant 保持同步)且安全开放;可选的定期清理是双重选择的(还需要 `MEMORY_TYPED_DECAY_SWEEP_INTERVAL>0`)。标志关闭时不会删除任何内容(Rule #20 精神)。新增 `src/lib/memory/typedDecay.ts`。回归测试:`tests/unit/memory/typed-decay.test.ts`(15 个)。v3.8.42 差距 — T10/TV6。 -- **dashboard (combos):** the named-combos editor now lets you **drag to reorder** the stacked-compression pipeline instead of only editing fixed-position steps. A new pure model (`src/shared/components/compression/compressionPipelineModel.ts`) owns add/remove/move/update with the engine→intensity invariant and a never-empty guarantee, and a `@dnd-kit/sortable` editor (`CompressionPipelineEditor.tsx`, matching the sidebar reorder pattern) replaces the inline list in `CompressionCombosPageClient`. Order persists through the existing combos endpoint. Regression guards: `tests/unit/compression-pipeline-model.test.ts` (11) + `tests/unit/ui/compression-pipeline-editor.test.tsx` (4). A dedicated `tests/e2e/compression-studio.spec.ts` (Tela A render + tab switch) closes the studios e2e gap the combo-live spec did not cover. gaps v3.8.42 — T06 + T03. +- **dashboard(组合):** 命名组合编辑器现在允许**拖拽排序**堆叠压缩管道,而不仅限于编辑固定位置的步骤。新增纯模型(`src/shared/components/compression/compressionPipelineModel.ts`)管理添加/移除/移动/更新,保持引擎→强度不变性和永不为空的保证,`@dnd-kit/sortable` 编辑器(`CompressionPipelineEditor.tsx`,匹配侧边栏重新排序模式)替代了 `CompressionCombosPageClient` 中的内联列表。顺序通过现有的组合端点持久化。回归测试:`tests/unit/compression-pipeline-model.test.ts`(11 个)+ `tests/unit/ui/compression-pipeline-editor.test.tsx`(4 个)。专用的 `tests/e2e/compression-studio.spec.ts`(Tela A 渲染 + 选项卡切换)填补了组合实时规范未覆盖的压缩工作室端到端测试空白。v3.8.42 差距 — T06 + T03。 -- **compression (pipeline):** add an **opt-in, default-off per-engine circuit-breaker** to the stacked compression pipeline (T02). When an engine throws repeatedly **across requests**, its breaker opens and the stacked loops skip that engine (keeping the body verbatim for that step — fail-open) for a cooldown, then probe once (lazy half-open); success closes it, a failed probe re-opens it. This is distinct from the provider circuit-breaker (`src/shared/utils/circuitBreaker.ts`, provider-scoped + DB-persisted) — the new `pipelineEngineBreaker.ts` is engine-scoped, process-local, and adds zero DB/IO on the hot path. It composes with the existing per-request TV1 bail-out (which skips within a single request); the breaker adds cross-request memory. **Default off** (`COMPRESSION_PIPELINE_BREAKER_ENABLED=false`) → byte-identical to the pre-breaker pipeline (a throwing engine still propagates unless TV1 is separately enabled). Configurable per-call, per-`CompressionConfig`, or via env (`_THRESHOLD`/`_COOLDOWN_MS`). Regression guard: `tests/unit/compression/pipeline-circuit-breaker.test.ts` (9, incl. a throwing-engine integration); existing strategySelector/bail-out suites stay green. gaps v3.8.42 — T02 (2.2). +- **compression(管道):** 为堆叠压缩管道新增**可选、默认关闭的每引擎断路器**(T02)。当引擎在**跨请求**中反复抛出异常时,其断路器打开,堆叠循环跳过该引擎(保持正文原样传递给该步骤 — 安全开放),冷却后探测一次(惰性半开);成功则关闭,探测失败则重新打开。这与服务商断路器不同(`src/shared/utils/circuitBreaker.ts`,服务商级别 + DB 持久化)— 新增的 `pipelineEngineBreaker.ts` 是引擎级别的、进程本地的,在热路径上零 DB/IO 开销。它与现有的每请求 TV1 保释机制(跳过单个请求内的步骤)配合使用;断路器增加了跨请求的记忆。**默认关闭**(`COMPRESSION_PIPELINE_BREAKER_ENABLED=false`)→ 与断路器前的管道字节级相同(除非 TV1 单独启用,否则抛出异常的引擎仍会传播)。可通过每次调用、每个 `CompressionConfig` 或环境变量(`_THRESHOLD`/`_COOLDOWN_MS`)配置。回归测试:`tests/unit/compression/pipeline-circuit-breaker.test.ts`(9 个,包含抛出异常引擎的集成测试);现有的 strategySelector/保释测试套件保持绿色。v3.8.42 差距 — T02(2.2)。 -- **compression (CCR):** the CCR retrieval-feedback (H8) is now **graduated** instead of a binary cliff. Previously a block retrieved `>= 3` times was flagged do-not-compress and everything below that stayed fully compressible. Now each prior retrieval raises a block's **effective `minChars` linearly** (`effectiveMinChars`), so frequently-retrieved content is compressed progressively less; the `>= 3` exclusion is preserved (as `Infinity`). The ramp is controlled by a `retrievalRampFactor` (default `2`, per-combo config or `COMPRESSION_CCR_RETRIEVAL_RAMP_FACTOR`); `1` reproduces the exact legacy binary behavior. Per-`(principal, hash)` isolation is unchanged. Regression guard: `tests/unit/compression/ccr-retrieval-ramp.test.ts` (12); existing CCR suites (51) stay green. gaps v3.8.42 — T08/H8. +- **compression(CCR):** CCR 检索反馈(H8)现在是**渐进式**的,而非二值悬崖。之前检索 `>= 3` 次的块被标记为不压缩,以下所有内容保持完全可压缩。现在每次先前的检索会**线性提高**块的**有效 `minChars`**(`effectiveMinChars`),因此频繁检索的内容压缩程度逐渐降低;`>= 3` 次排除被保留(作为 `Infinity`)。斜率由 `retrievalRampFactor` 控制(默认 `2`,每个组合配置或 `COMPRESSION_CCR_RETRIEVAL_RAMP_FACTOR`);`1` 可重现确切的旧版二值行为。每 `(principal, hash)` 的隔离不变。回归测试:`tests/unit/compression/ccr-retrieval-ramp.test.ts`(12 个);现有 CCR 套件(51 个)保持绿色。v3.8.42 差距 — T08/H8。 -- **compression (cache-aware):** add an **opt-in, default-off usage-observed prefix freeze** (H5). The cache-aware guard previously preserved the system prompt only for providers a _static_ heuristic recognized as caching. It now also **learns** which system prompts actually recur: once a system prompt has been observed `>=` a threshold across requests, it is treated as a stable cacheable prefix and preserved from compression **even for providers the static check misses** — recovering prompt-cache hits that a prefix-compressing mode would otherwise bust. Content-addressed by a hash of the system prompt (OpenAI / Claude / Gemini shapes), in-memory + bounded, zero DB/IO; a "freeze" only _preserves_ the prefix, so it never mutates a payload. Default OFF (`COMPRESSION_PREFIX_FREEZE_ENABLED`, threshold `_THRESHOLD`); respects the `never` preserve-mode (never freezes). New `open-sse/services/compression/prefixFreeze.ts`, wired into `resolveCacheAwareConfig`. Regression guard: `tests/unit/compression/prefix-freeze.test.ts` (10); 44 existing cache-aware / preserve-mode tests stay green. gaps v3.8.42 — T08/H5. +- **compression(缓存感知):** 新增**可选、默认关闭的基于使用量的前缀冻结**(H5)。缓存感知保护之前仅对静态启发式识别为缓存的提供者保留系统提示。现在它还会**学习**哪些系统提示实际上会重复出现:一旦系统提示在跨请求中被观察到 `>=` 某个阈值次数,它就被视为稳定的可缓存前缀,并受到压缩保护 — **即使对于静态检查遗漏的提供者也是如此** — 恢复前缀压缩模式会破坏的提示缓存命中。通过系统提示的哈希进行内容寻址(OpenAI / Claude / Gemini 格式),内存中 + 有界,零 DB/IO;"冻结"仅保留前缀,因此永远不会改变有效载荷。默认关闭(`COMPRESSION_PREFIX_FREEZE_ENABLED`,阈值 `_THRESHOLD`);遵守 `never` 保留模式(永不解冻)。新增 `open-sse/services/compression/prefixFreeze.ts`,接入 `resolveCacheAwareConfig`。回归测试:`tests/unit/compression/prefix-freeze.test.ts`(10 个);44 个现有缓存感知/保留模式测试保持绿色。v3.8.42 差距 — T08/H5。 -- **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7. +- **compression(读取生命周期):** 新增**可选、默认关闭的 `read-lifecycle` 引擎**(H7),用于折叠**过时/被取代的文件读取工具结果**。在代理对话中,同一个文件被反复读取;一旦同一路径被**重新读取**(被更新的视图取代)或被后续的写入/编辑**修改**,先前的读取就变为过时。该引擎用简短占位符替换那些先前的读取结果 — 仅保留当前(最后、未被取代的)读取完整 — 回收模型不再需要的 token。与 `session-dedup`(相同内容)或 `ccr`(可逆标记)不同,这是语义性的 + **有损的**,因此是可选功能(`enabled` 默认 `false`)。构造上保守:仅匹配已知的读取/写入工具名称,比较精确路径,仅在严格较晚的调用触及相同路径时折叠读取,遇到任何意外形状时**安全开放**。支持 **Anthropic**(`tool_use`/`tool_result`)和 **OpenAI**(`tool_calls` + `role:"tool"`)两种格式。新增 `open-sse/services/compression/engines/readLifecycle/index.ts`。回归测试:`tests/unit/compression/read-lifecycle.test.ts`(10 个)。v3.8.42 差距 — T08/H7。 -- **observability (correlation IDs):** requests now carry a correlation id threaded through logs so a single request can be traced end-to-end across the pipeline. ([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — thanks @hartmark) +- **observability(关联 ID):** 请求现在携带关联 ID,贯穿日志,使单个请求可以在管道中端到端追踪。([#5834](https://github.com/diegosouzapw/OmniRoute/pull/5834) — 感谢 @hartmark) -- **cli (startup banner — boot time):** the `serve` ready banner now shows how long startup took, so slow-boot conditions are visible at a glance. ([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — thanks @ishatiwari21) +- **cli(启动横幅 — 启动耗时):** `serve` 就绪横幅现在显示启动耗时,使慢启动情况一目了然。([#5799](https://github.com/diegosouzapw/OmniRoute/pull/5799) — 感谢 @ishatiwari21) -- **api (quota-policy bypass scope):** add an **opt-in** API-key provider quota-policy bypass scope, so a designated key can be exempted from provider quota enforcement without disabling quotas globally. ([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — thanks @Witroch4) +- **api(配额策略绕过作用域):** 新增**可选**的 API 密钥服务商配额策略绕过作用域,使指定密钥可在不禁用全局配额的情况下免于服务商配额执行。([#5731](https://github.com/diegosouzapw/OmniRoute/pull/5731) — 感谢 @Witroch4) -- **providers (Ollama local):** add a first-class **Ollama** local-provider card to the providers dashboard so the local LLM runtime can be configured like any other provider. ([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — thanks @diegosouzapw) +- **providers(Ollama 本地):** 在服务商仪表盘上新增一流的 **Ollama** 本地服务商卡片,使本地 LLM 运行时可以像其他服务商一样配置。([#5712](https://github.com/diegosouzapw/OmniRoute/pull/5712) — 感谢 @diegosouzapw) -- **codex (fallback profiles):** generate fallback CLI profiles for Codex-compatible models so compatible models get a usable profile automatically. ([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — thanks @skyzea1) +- **codex(回退配置文件):** 为 Codex 兼容模型生成回退 CLI 配置文件,使兼容模型自动获得可用的配置文件。([#5701](https://github.com/diegosouzapw/OmniRoute/pull/5701) — 感谢 @skyzea1) -- **api (response-body validation + failover):** add a **configurable response-body validation** step that can fail a target over to the next candidate when the upstream returns a structurally-invalid body (routing/#4985). ([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — thanks @diegosouzapw) +- **api(响应体验证 + 故障转移):** 新增**可配置的响应体验证**步骤,当上游返回结构无效的响应体时,可将该目标故障转移到下一个候选项(routing/#4985)。([#5684](https://github.com/diegosouzapw/OmniRoute/pull/5684) — 感谢 @diegosouzapw) -- **providers (SenseNova):** complete the SenseNova free **Token Plan** — chat completions plus **Text-to-Image** (ported from 9router#2233). ([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — thanks @diegosouzapw) +- **providers(SenseNova):** 完善 SenseNova 免费**Token 计划** — 聊天补全加上**文本到图像**(从 9router#2233 移植)。([#5679](https://github.com/diegosouzapw/OmniRoute/pull/5679) — 感谢 @diegosouzapw) -- **db (self-correcting context windows):** add **self-correcting model context-window overrides** so a model whose advertised context length is wrong is corrected automatically (models/#5004). ([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — thanks @diegosouzapw) +- **db(自校正上下文窗口):** 新增**自校正模型上下文窗口覆盖**,使广告的上下文长度有误的模型能自动纠正(models/#5004)。([#5667](https://github.com/diegosouzapw/OmniRoute/pull/5667) — 感谢 @diegosouzapw) -- **routing (latency strategy):** optimize the latency routing strategy using observed per-target performance metrics for better candidate selection. ([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — thanks @KooshaPari) +- **routing(延迟策略):** 使用观测到的每目标性能指标优化延迟路由策略,以获得更好的候选选择。([#5629](https://github.com/diegosouzapw/OmniRoute/pull/5629) — 感谢 @KooshaPari) -- **compression (preserveSystemPrompt mode):** add a `preserveSystemPrompt` mode enum (`always` | `whenNoCache` | `never`) with legacy back-compat, giving operators explicit control over when the system prompt is protected from compression (T05/C5). ([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — thanks @diegosouzapw) +- **compression(preserveSystemPrompt 模式):** 新增 `preserveSystemPrompt` 模式枚举(`always` | `whenNoCache` | `never`),保留旧版兼容性,使操作者可以明确控制系统提示何时受到压缩保护(T05/C5)。([#5653](https://github.com/diegosouzapw/OmniRoute/pull/5653) — 感谢 @diegosouzapw) -- **commandCode (vision):** add multimodal **image** support for Command Code vision models. ([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — thanks @Stazyu) +- **commandCode(视觉):** 为 Command Code 视觉模型新增多模态**图像**支持。([#5557](https://github.com/diegosouzapw/OmniRoute/pull/5557) — 感谢 @Stazyu) -- **compression (read-lifecycle engine):** T08/H7 (2.5) — an opt-in read-lifecycle engine that collapses superseded file reads so stale earlier reads of the same file are pruned from the context. ([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — thanks @diegosouzapw) +- **compression(读取生命周期引擎):** T08/H7(2.5)— 可选读取生命周期引擎,折叠被取代的文件读取,使同一文件的过时早期读取从上下文中被裁剪。([#5754](https://github.com/diegosouzapw/OmniRoute/pull/5754) — 感谢 @diegosouzapw) -- **compression (usage-observed prefix freeze):** T08/H5 (2.4) — opt-in prefix freeze driven by observed usage, keeping a stable cached prefix from being rewritten by downstream engines. ([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — thanks @diegosouzapw) +- **compression(基于使用量的前缀冻结):** T08/H5(2.4)— 由观测到的使用量驱动的可选前缀冻结,保持稳定的缓存前缀不被下游引擎重写。([#5744](https://github.com/diegosouzapw/OmniRoute/pull/5744) — 感谢 @diegosouzapw) -- **compression (CCR retrieval-feedback ramp):** T08/H8 (2.3) — a graduated Context-Compression-Ratio retrieval-feedback ramp that tunes compression aggressiveness from retrieval signals. ([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — thanks @diegosouzapw) +- **compression(CCR 检索反馈斜率):** T08/H8(2.3)— 渐进式上下文压缩比检索反馈斜率,根据检索信号调整压缩激进程度。([#5739](https://github.com/diegosouzapw/OmniRoute/pull/5739) — 感谢 @diegosouzapw) -- **compression (per-engine circuit breaker):** T02 — an opt-in per-engine pipeline circuit-breaker that disables a misbehaving compression engine without failing the whole pipeline. ([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — thanks @diegosouzapw) +- **compression(每引擎断路器):** T02 — 可选每引擎管道断路器,禁用行为异常的压缩引擎而不使整个管道失败。([#5735](https://github.com/diegosouzapw/OmniRoute/pull/5735) — 感谢 @diegosouzapw) -- **compression (LLM-tier engine):** T05/C3 — an opt-in LLM-tier compression engine that uses a model pass for higher-ratio semantic compression. ([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — thanks @diegosouzapw) +- **compression(LLM 层级引擎):** T05/C3 — 可选 LLM 层级压缩引擎,使用模型通道进行更高比率的语义压缩。([#5702](https://github.com/diegosouzapw/OmniRoute/pull/5702) — 感谢 @diegosouzapw) -- **dashboard (compression pipeline editor):** T06/T03 — a drag-to-reorder compression pipeline editor plus a compression-studio e2e flow. ([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — thanks @diegosouzapw) +- **dashboard(压缩管道编辑器):** T06/T03 — 拖拽排序的压缩管道编辑器加上压缩工作室端到端流程。([#5727](https://github.com/diegosouzapw/OmniRoute/pull/5727) — 感谢 @diegosouzapw) -- **memory (typed decay):** T10/TV6 — opt-in typed memory decay so aged, low-value memories fade on a per-type schedule. ([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — thanks @diegosouzapw) +- **memory(类型化衰减):** T10/TV6 — 可选类型化记忆衰减,使陈旧、低价值的记忆按类型时间表逐渐消失。([#5723](https://github.com/diegosouzapw/OmniRoute/pull/5723) — 感谢 @diegosouzapw) -- **mcp (RTK tools):** T07 — expose the RTK learn/discover capabilities as first-class MCP tools. ([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — thanks @diegosouzapw) +- **mcp(RTK 工具):** T07 — 将 RTK 学习/发现能力作为一流的 MCP 工具暴露。([#5691](https://github.com/diegosouzapw/OmniRoute/pull/5691) — 感谢 @diegosouzapw) -- **providers (CLI profile auto-sync):** opt-in CLI profile auto-sync toggles, including Claude Code auto-sync, so generated CLI profiles can track provider changes automatically. ([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — thanks @diegosouzapw) +- **providers(CLI 配置文件自动同步):** 可选 CLI 配置文件自动同步开关,包括 Claude Code 自动同步,使生成的 CLI 配置文件可以自动跟踪服务商变更。([#5755](https://github.com/diegosouzapw/OmniRoute/pull/5755) — 感谢 @diegosouzapw) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **fix(opencode):** stop fabricating `User-Agent: opencode/local` and `x-opencode-client: cli` headers when the client sends none — the executor-dedup refactor ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720)) accidentally re-introduced header fabrication, violating the forward-only contract (inventing opencode-internal values risks upstream rejection). Restored to forward-only: those headers are emitted only when a real client source is present. Regression guard: `tests/unit/opencode-executor.test.ts`. (thanks @diegosouzapw) +- **fix(opencode):** 停止在客户端未发送时伪造 `User-Agent: opencode/local` 和 `x-opencode-client: cli` 请求头 — executor 去重重构([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720))意外地重新引入了请求头伪造,违反了仅转发契约(发明 opencode 内部值有被上游拒绝的风险)。恢复为仅转发:仅当存在真实客户端来源时才发送这些请求头。回归测试:`tests/unit/opencode-executor.test.ts`。(感谢 @diegosouzapw) -- **fix(executors):** `resolveEffectiveKey` returns `undefined` (not `""`) when no API key is present — a type-coercion cleanup ([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798)) changed `apiKey ?? ""` to satisfy the typechecker, silently mutating auth-key resolution semantics. Widened the return type to `string | undefined` and reverted the coercion so OAuth-only credentials resolve correctly. Regression guard: `tests/unit/refactor-buildHeaders-preamble.test.ts`. (thanks @diegosouzapw) +- **fix(executors):** `resolveEffectiveKey` 在没有 API 密钥时返回 `undefined`(而非 `""`)— 类型强制清理([#5798](https://github.com/diegosouzapw/OmniRoute/pull/5798))将 `apiKey ?? ""` 改为满足类型检查器,静默地改变了认证密钥解析语义。将返回类型加宽为 `string | undefined` 并撤销了强制转换,使仅 OAuth 凭据能正确解析。回归测试:`tests/unit/refactor-buildHeaders-preamble.test.ts`。(感谢 @diegosouzapw) -- **fix(translator):** restore the terminal `message_delta` + `message_stop` on Responses→Claude streams — the doubled-tool-args dedup ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828)) guarded the finish handler on the shared `state.finishReason`, which the openai-responses→openai leg sets first in the hub path, so the openai→claude leg dropped its terminal events and the stream ended after `content_block_delta`. The dedup now uses a dedicated `state.claudeFinishEmitted` flag. Regression guard: `tests/unit/claude-code-rendering-fixes.test.ts`. (thanks @diegosouzapw) +- **fix(translator):** 恢复 Responses→Claude 流上的终端 `message_delta` + `message_stop` — 工具参数重复去重([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828))在共享的 `state.finishReason` 上守卫了完成处理程序,而 openai-responses→openai 通道在枢纽路径中首先设置该值,因此 openai→claude 通道丢弃了其终端事件,流在 `content_block_delta` 后结束。去重现在使用专用的 `state.claudeFinishEmitted` 标志。回归测试:`tests/unit/claude-code-rendering-fixes.test.ts`。(感谢 @diegosouzapw) -- **fix(pricing):** add the Kiro `claude-sonnet-5` pricing row so the newly-catalogued model ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796)) no longer reports `$0.00` usage. Regression guard: `tests/unit/catalog-updates-v3x.test.ts`. (thanks @diegosouzapw) +- **fix(pricing):** 新增 Kiro `claude-sonnet-5` 定价行,使新编目的模型([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796))不再报告 `$0.00` 用量。回归测试:`tests/unit/catalog-updates-v3x.test.ts`。(感谢 @diegosouzapw) -- **fix(github):** keep Copilot access-token sessions active. GitHub Copilot device-flow accounts can hold a GitHub access token plus a short-lived Copilot token **without** a refresh token; the proactive health check treated that as terminal `no_refresh_token` and marked the connection expired minutes after login. The health check now keeps those sessions active, clears stale `no_refresh_token` state, and refreshes the Copilot sub-token when needed. Regression guard: `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. Extracted from [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863) by [@Witroch4](https://github.com/Witroch4). +- **fix(github):** 保持 Copilot 访问令牌会话活跃。GitHub Copilot 设备流账户可能持有 GitHub 访问令牌加上短期 Copilot 令牌,而**没有**刷新令牌;主动健康检查将其视为终端 `no_refresh_token`,并在登录后几分钟标记连接过期。健康检查现在保持这些会话活跃,清除过时的 `no_refresh_token` 状态,并在需要时刷新 Copilot 子令牌。回归测试:`tests/unit/token-health-no-refresh-token-expired-5326.test.ts`。提取自 [#5863](https://github.com/diegosouzapw/OmniRoute/pull/5863),由 [@Witroch4](https://github.com/Witroch4) 贡献。 -- **fix(kiro):** bound the Claude model-id dash→dot normalization to a 1–2 digit minor so date-suffixed ids (e.g. claude-opus-4-20250514) are no longer corrupted. (thanks @voravitl) +- **fix(kiro):** 将 Claude 模型 ID 破折号→点号规范化限制为 1–2 位副版本号,使日期后缀 ID(例如 claude-opus-4-20250514)不再被损坏。(感谢 @voravitl) -- **fix(usage):** preserve (bounded) tool definitions in request logs even when the request body is truncated, so the request-details view can still show available tools. (thanks @noir017) +- **fix(usage):** 即使在请求体被截断时,也在请求日志中保留(有界的)工具定义,使请求详情视图仍能显示可用工具。(感谢 @noir017) -- **fix(providers):** route OpenAI responses-only models to `/v1/responses` instead of 404ing on `/v1/chat/completions`. The curated `gpt-5.5-pro` / `gpt-5.4-pro` entries never worked (OpenAI only serves `*-pro` reasoning models via the Responses API), and "Test all models" surfaced the same 404s. The registry entries now carry `targetFormat: "openai-responses"` (reusing the existing per-model translation plumbing shared with `gh`/`codex`), `DefaultExecutor.buildUrl` swaps the `openai` endpoint to `/responses` in lockstep (honoring custom base URLs), and a `-pro` suffix heuristic covers dynamically-synced ids such as `o1-pro` / `gpt-5.2-pro` (same spirit as the gh executor's `/codex/i` routing, 9router#102). Legacy completions-only ids (e.g. `gpt-3.5-turbo-instruct`) are out of scope — they are not in the catalog and OmniRoute has no legacy `/v1/completions` upstream. Regression guard: `tests/unit/openai-responses-only-models-5842.test.ts` (8). Thanks [@maikokan](https://github.com/maikokan). ([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842)) +- **fix(providers):** 将 OpenAI 仅响应模型路由到 `/v1/responses` 而非在 `/v1/chat/completions` 上 404。策划的 `gpt-5.5-pro` / `gpt-5.4-pro` 条目从未工作过(OpenAI 仅通过 Responses API 提供 `*-pro` 推理模型),"测试所有模型"也显示相同的 404。注册表条目现在携带 `targetFormat: "openai-responses"`(复用与 `gh`/`codex` 共享的现有每模型翻译管道),`DefaultExecutor.buildUrl` 将 `openai` 端点同步替换为 `/responses`(遵循自定义基础 URL),`-pro` 后缀启发式覆盖动态同步的 ID,如 `o1-pro` / `gpt-5.2-pro`(与 gh executor 的 `/codex/i` 路由相同精神,9router#102)。旧版仅补全 ID(例如 `gpt-3.5-turbo-instruct`)不在范围内 — 它们不在目录中,OmniRoute 也没有旧版 `/v1/completions` 上游。回归测试:`tests/unit/openai-responses-only-models-5842.test.ts`(8 个)。感谢 [@maikokan](https://github.com/maikokan)。([#5842](https://github.com/diegosouzapw/OmniRoute/issues/5842)) -- **fix(image):** keep bare codex image aliases (e.g. `gpt-5.5`) resolving to the codex image pipeline even when a combo shares the same name. A chat combo named `gpt-5.5` used to shadow the bare image alias in `resolveImageRouteModel`, hijacking `/v1/images/*` requests to a chat target (regression path adjacent to [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)); codex bare models are now reserved before bare-combo resolution, while non-codex aliases (e.g. `gpt-image-2`) remain user-shadowable (#3214/#3215 behavior preserved). Regression guard: `tests/unit/image-routes-combo-edits-3214-3215.test.ts` (9). ([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902) by [@KooshaPari](https://github.com/KooshaPari)) +- **fix(image):** 保持裸 codex 图像别名(例如 `gpt-5.5`)解析到 codex 图像管道,即使组合共享相同名称。名为 `gpt-5.5` 的聊天组合曾经在 `resolveImageRouteModel` 中遮蔽裸图像别名,劫持 `/v1/images/*` 请求到聊天目标(与 [#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887) 相邻的回归路径);codex 裸模型现在在裸组合解析之前被保留,而非 codex 别名(例如 `gpt-image-2`)仍可被用户遮蔽(#3214/#3215 行为保留)。回归测试:`tests/unit/image-routes-combo-edits-3214-3215.test.ts`(9 个)。([#5902](https://github.com/diegosouzapw/OmniRoute/pull/5902),由 [@KooshaPari](https://github.com/KooshaPari) 贡献) -- **fix(ci):** re-green the `release/v3.8.43` fast-gates queue — every PR→release was inheriting base-reds ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)). Five distinct blockers cleared: (1) stale `modelContextOverrides` entry in the `check:db-rules` intentionally-internal allowlist ([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) allowlisted it while the [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) fix re-exported it from `localDb.ts`; the re-export stays, the obsolete entry goes, classification guard re-pinned to 33); (2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` documented in `docs/reference/ENVIRONMENT.md` (env/docs contract, from [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877)); (3) the Router Backends ADR's references to the not-yet-merged registry ([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) marked as landing-with-PR so `check:fabricated-docs --strict` passes; (4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` added to stryker `tap.testFiles` (`check:mutation-test-coverage`); (5) file-size / complexity / cognitive-complexity ratchets rebaselined with justification notes — all drift measured identical on the pristine tip and this PR (net-zero). Regression guard: `tests/unit/check-db-rules-classification.test.ts`. ([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)) +- **fix(ci):** 重新打通 `release/v3.8.43` 快速门禁队列 — 每个 PR→发布都在继承基础红色([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798))。清除了五个不同的阻塞项:(1) `check:db-rules` 有意内部化白名单中过时的 `modelContextOverrides` 条目([#5827](https://github.com/diegosouzapw/OmniRoute/pull/5827) 将其加入白名单,而 [#5609](https://github.com/diegosouzapw/OmniRoute/issues/5609) 修复将其从 `localDb.ts` 重新导出;重新导出保留,过时条目移除,分类守卫重新固定为 33);(2) `LIVE_WS_ALLOWED_HOSTS` / `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL` 在 `docs/reference/ENVIRONMENT.md` 中文档化(环境/文档契约,来自 [#5877](https://github.com/diegosouzapw/OmniRoute/pull/5877));(3) 路由器后端 ADR 对尚未合并的注册表([#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868))的引用标记为随 PR 落地,使 `check:fabricated-docs --strict` 通过;(4) `antigravity-429-quota-tdd` + `middleware-header-strip-5849` 添加到 stryker `tap.testFiles`(`check:mutation-test-coverage`);(5) 文件大小/复杂度/认知复杂度基准重新设定,附有理由说明 — 所有漂移在原始分支尖和此 PR 上测量完全一致(净零)。回归测试:`tests/unit/check-db-rules-classification.test.ts`。([#5798](https://github.com/diegosouzapw/OmniRoute/issues/5798)) -- **providers (codex image auto-routing regression):** an unprefixed `gpt-5.5` request from a codex-only setup (no OpenAI connection) now correctly infers the `codex` provider again — the OpenAI static-catalog short-circuit in `resolveModelByProviderInference` was preempting the codex-preference block, so `gpt-5.5` (added to the OpenAI catalog) stopped auto-routing to Codex image generation. Users with an active OpenAI connection are unaffected (OpenAI stays default). Regression guard: `tests/unit/codex-gpt55-routing-5887.test.ts`. ([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)) +- **providers(codex 图像自动路由回归):** 来自仅 codex 设置(无 OpenAI 连接)的无前缀 `gpt-5.5` 请求现在再次正确推断 `codex` 提供者 — OpenAI 静态目录在 `resolveModelByProviderInference` 中的短路正在抢占 codex 偏好块,因此 `gpt-5.5`(已添加到 OpenAI 目录)停止自动路由到 Codex 图像生成。具有活跃 OpenAI 连接的用户不受影响(OpenAI 保持默认)。回归测试:`tests/unit/codex-gpt55-routing-5887.test.ts`。([#5887](https://github.com/diegosouzapw/OmniRoute/issues/5887)) -- **api (proxy header hygiene):** upstream `x-middleware-*` control headers (emitted by providers hosted behind Next.js, e.g. synthetic.new) are now stripped from proxied responses instead of forwarded verbatim — forwarding `x-middleware-rewrite` made Next 16 throw `NextResponse.rewrite() was used in a app route handler` and return 500 despite a successful upstream call. Applies to both streaming and JSON paths. Regression guard: `tests/unit/middleware-header-strip-5849.test.ts`. ([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849)) +- **api(代理请求头清理):** 上游 `x-middleware-*` 控制请求头(由托管在 Next.js 后面的提供者发出,例如 synthetic.new)现在从代理响应中被剥离,而非逐字转发 — 转发 `x-middleware-rewrite` 使 Next 16 抛出 `NextResponse.rewrite() was used in a app route handler` 并返回 500,尽管上游调用成功。适用于流式和 JSON 路径。回归测试:`tests/unit/middleware-header-strip-5849.test.ts`。([#5849](https://github.com/diegosouzapw/OmniRoute/issues/5849)) -- **docs (pnpm global install):** replaced the unsupported `pnpm approve-builds -g` step with the install-time `pnpm add -g omniroute@latest --allow-build=better-sqlite3` flag across README + Setup Guide (and i18n mirrors), fixing native-build approval for pnpm v11 global installs. ([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554)) +- **docs(pnpm 全局安装):** 将不受支持的 `pnpm approve-builds -g` 步骤替换为安装时的 `pnpm add -g omniroute@latest --allow-build=better-sqlite3` 标志,覆盖 README + 设置指南(及 i18n 镜像),修复 pnpm v11 全局安装的原生构建批准问题。([#5554](https://github.com/diegosouzapw/OmniRoute/issues/5554)) -- **dashboard (token badge):** the red "Token Expired" connection badge no longer flashes for OAuth refresh-capable providers (Antigravity/Gemini) whose access token merely lapsed but is auto-refreshed — it now shows only when the connection is terminally expired (`testStatus === "expired"`). Continuation of #5326. Regression guard: `tests/unit/ui/connection-row-token-badge-5836.test.tsx`. ([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836)) +- **dashboard(令牌徽章):** 红色"令牌已过期"连接徽章不再对支持 OAuth 刷新的提供者(Antigravity/Gemini)闪烁,这些提供者的访问令牌仅过期但会自动刷新 — 现在仅在连接终端过期时显示(`testStatus === "expired"`)。#5326 的延续。回归测试:`tests/unit/ui/connection-row-token-badge-5836.test.tsx`。([#5836](https://github.com/diegosouzapw/OmniRoute/issues/5836)) -- **db (auto backup toggle):** full pre-write SQLite backups now honor the persisted `backup.autoBackupEnabled` dashboard setting — previously only the `DISABLE_SQLITE_AUTO_BACKUP` env var was checked, so disabling auto-backup in the UI had no effect and ~70MB pre-write snapshots kept firing. Manual and pre-restore backups still always run. Regression guard: `tests/unit/db-backup-autobackup-setting-5871.test.ts`. ([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871)) +- **db(自动备份开关):** 完整的写入前 SQLite 备份现在遵循持久化的 `backup.autoBackupEnabled` 仪表盘设置 — 之前只检查 `DISABLE_SQLITE_AUTO_BACKUP` 环境变量,因此在 UI 中禁用自动备份无效,约 70MB 的写入前快照仍在不断触发。手动和恢复前备份仍然始终运行。回归测试:`tests/unit/db-backup-autobackup-setting-5871.test.ts`。([#5871](https://github.com/diegosouzapw/OmniRoute/issues/5871)) -- **providers (auto/ routing for custom providers):** custom OpenAI-/Anthropic-compatible providers (dynamic `*-compatible-*` connection IDs) are no longer excluded from `auto/` routing — the Auto-Combo virtual factory previously skipped any connection whose provider was absent from the static registry. It now falls back to the connection's `defaultModel`. Regression guard: `tests/unit/auto-custom-provider-5873.test.ts`. ([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873)) +- **providers(自定义提供者的 auto/ 路由):** 自定义 OpenAI-/Anthropic-兼容提供者(动态 `*-compatible-*` 连接 ID)不再被排除在 `auto/` 路由之外 — Auto-Combo 虚拟工厂之前跳过任何提供者不在静态注册表中的连接。现在回退到连接的 `defaultModel`。回归测试:`tests/unit/auto-custom-provider-5873.test.ts`。([#5873](https://github.com/diegosouzapw/OmniRoute/issues/5873)) -- **middleware (hook sandbox):** operator-authored pre-request hook code now runs inside a hardened Node `vm` sandbox (minimal context, no ambient globals/`process.env`, execution timeout, no `require`) instead of `new Function()` in the main process — closing the Hard Rule #3 / SonarCloud S1523 exposure. Regression guard: `tests/unit/middleware-hook-sandbox-5872.test.ts`. ([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872)) +- **middleware(Hook 沙箱):** 操作者编写的预请求 Hook 代码现在在强化的 Node `vm` 沙箱中运行(最小上下文、无环境全局变量/`process.env`、执行超时、无 `require`),而非在主进程中通过 `new Function()` — 关闭了 Hard Rule #3 / SonarCloud S1523 暴露面。回归测试:`tests/unit/middleware-hook-sandbox-5872.test.ts`。([#5872](https://github.com/diegosouzapw/OmniRoute/issues/5872)) -- **mcp-server (auth forwarding):** the per-caller MCP identity forwarded via `withMcpHttpAuthContext` now wins over the static `OMNIROUTE_API_KEY` env fallback in the internal-fetch helpers (`apiFetch`, `omniRouteFetch`) — previously the env key was spread after the forwarded headers and clobbered the caller's `Authorization`. Regression guard: `open-sse/mcp-server/__tests__/httpAuthContext.test.ts`. ([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819)) +- **mcp-server(认证转发):** 通过 `withMcpHttpAuthContext` 转发的每调用者 MCP 身份现在优先于内部 fetch 辅助函数(`apiFetch`、`omniRouteFetch`)中的静态 `OMNIROUTE_API_KEY` 环境变量回退 — 之前环境变量密钥在转发请求头之后被展开,覆盖了调用者的 `Authorization`。回归测试:`open-sse/mcp-server/__tests__/httpAuthContext.test.ts`。([#5819](https://github.com/diegosouzapw/OmniRoute/issues/5819)) -- **dashboard (Modal provider — two-field auth):** the Modal provider connection form now exposes **two fields — Token ID + Token Secret —** instead of a single API-key input, since Modal authenticates with `Authorization: Bearer :`. The dashboard combines the two fields into the `id:secret` credential before saving (`combineModalCredential`, trims both parts), while a value pasted in the legacy single-field format keeps working verbatim (empty secret → passthrough), so existing saved connections need no migration; the key-help link points at Modal's token settings. Regression guard: `tests/unit/modal-credential-combine.test.ts` (5). ([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881), closes [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446)) Follow-up: the **Validation Model Id** field is now pre-filled for Modal with the same model the server-side validator probes (`Qwen/Qwen3-4B-Thinking-2507-FP8`, shared via `MODAL_DEFAULT_VALIDATION_MODEL_ID` in `src/shared/constants/modal.ts`), closing the last checklist item of #5446. Regression guard: `tests/unit/modal-validation-model-prefill.test.ts`. +- **dashboard(Modal 提供者 — 双字段认证):** Modal 提供者连接表单现在暴露**两个字段 — Token ID + Token Secret —** 而非单一的 API 密钥输入,因为 Modal 使用 `Authorization: Bearer :` 进行认证。仪表盘将两个字段合并为 `id:secret` 凭据后保存(`combineModalCredential`,修剪两部分),而在旧版单字段格式中粘贴的值保持逐字工作(空 secret → 透传),因此现有已保存的连接无需迁移;密钥帮助链接指向 Modal 的令牌设置。回归测试:`tests/unit/modal-credential-combine.test.ts`(5 个)。([#5881](https://github.com/diegosouzapw/OmniRoute/pull/5881),关闭 [#5446](https://github.com/diegosouzapw/OmniRoute/issues/5446))后续:**验证模型 ID** 字段现在为 Modal 预填了服务端验证器探测的相同模型(`Qwen/Qwen3-4B-Thinking-2507-FP8`,通过 `src/shared/constants/modal.ts` 中的 `MODAL_DEFAULT_VALIDATION_MODEL_ID` 共享),关闭 #5446 的最后一项清单条目。回归测试:`tests/unit/modal-validation-model-prefill.test.ts`。 -- **api (chat completions — early SSE keepalive gate):** the `/v1/chat/completions` route wrapped the response in the early-stream keepalive whenever `stream` was not explicitly `false`, so a client that omitted `stream` and asked for JSON (`Accept: application/json`) could receive premature SSE framing. The keepalive wrapper is now gated on an explicit `stream: true` in the body or an Accept header that forces SSE (`acceptHeaderForcesStream`); the parsed body is passed to the chat handler untouched, so the actual stream/JSON framing stays decided by `chatCore`/`resolveStreamFlag` — preserving OmniRoute's legacy streaming default when `stream` is omitted and the per-key `streamDefaultMode: "json"` opt-in. Regression guard: `tests/unit/chat-combo-live-test.test.ts` ("returns JSON without early SSE framing when stream is omitted and Accept is application/json"). ([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866) by [@rdself](https://github.com/rdself)) +- **api(聊天补全 — 早期 SSE 保活门控):** `/v1/chat/completions` 路由在 `stream` 未显式为 `false` 时将响应包装在早期流保活中,因此省略 `stream` 并请求 JSON 的客户端(`Accept: application/json`)可能收到过早的 SSE 帧。保活包装器现在受显式 `stream: true` 或强制 SSE 的 Accept 请求头(`acceptHeaderForcesStream`)的门控;解析后的请求体原样传递给聊天处理程序,因此实际的流/JSON 帧仍由 `chatCore`/`resolveStreamFlag` 决定 — 保留 OmniRoute 在省略 `stream` 时的旧版流默认行为和每密钥 `streamDefaultMode: "json"` 选择。回归测试:`tests/unit/chat-combo-live-test.test.ts`("省略 stream 且 Accept 为 application/json 时返回 JSON 而无早期 SSE 帧")。([#5866](https://github.com/diegosouzapw/OmniRoute/pull/5866),由 [@rdself](https://github.com/rdself) 贡献) -- **fix(github):** drop a trailing assistant prefill before dispatching to GitHub Copilot chat to avoid 400 errors. (thanks @baslr) +- **fix(github):** 在派发到 GitHub Copilot 聊天前丢弃尾随的 assistant 预填充,以避免 400 错误。(感谢 @baslr) -- **fix(oauth):** prevent cross-IdP account overwrites by disambiguating OAuth connections on `username` when present, not email alone. (thanks @KunN-21) +- **fix(oauth):** 通过在存在 `username` 时以此字段而非仅用 email 来消除 OAuth 连接歧义,防止跨 IdP 账户覆盖。(感谢 @KunN-21) -- **fix(mitm):** best-effort revert privileged /etc/hosts entries on exit when a sudo password is cached, instead of always leaving orphaned state. (thanks @manhdzzz) +- **fix(mitm):** 在 sudo 密码缓存时尽力在退出时还原特权 /etc/hosts 条目,而非始终留下孤立状态。(感谢 @manhdzzz) -- **providers (Kiro — Claude Sonnet 5):** the Kiro provider's model catalog was missing `claude-sonnet-5`, so the model could not be selected or routed even on accounts that already had access to it ("claude-sonnet-5 is not supported"). Added the model to the Kiro registry (`open-sse/config/providers/registry/kiro/index.ts`) as a 1M-context / 128K-output Claude model, mirroring the existing Claude entries; the registry `models[]` feeds both the model selector and the live CodeWhisperer `ListAvailableModels` fallback, so the model is now selectable and routable. Regression guard: `tests/unit/kiro-claude-sonnet-5-2267.test.ts`. (thanks [@openbioinfo](https://github.com/openbioinfo)) +- **providers(Kiro — Claude Sonnet 5):** Kiro 提供者的模型目录缺少 `claude-sonnet-5`,因此即使账户已有访问权限,该模型也无法被选择或路由("claude-sonnet-5 is not supported")。将模型添加到 Kiro 注册表(`open-sse/config/providers/registry/kiro/index.ts`),作为 1M 上下文 / 128K 输出的 Claude 模型,镜像现有 Claude 条目;注册表 `models[]` 同时供给模型选择器和实时 CodeWhisperer `ListAvailableModels` 回退,因此该模型现在可选择和可路由。回归测试:`tests/unit/kiro-claude-sonnet-5-2267.test.ts`。(感谢 [@openbioinfo](https://github.com/openbioinfo)) -- **settings (model aliases — self-heal after restart):** the Settings → Routing page showed "No exact-match aliases configured" after a server restart even though the aliases were persisted in the DB. Aliases are held in a module-local `_customAliases` map in `modelDeprecation.ts` that the boot path hydrates, but Next.js compiles the app-route module graph separately from the startup graph (the same webpack chunk-splitting class as #5312), so the `GET /api/settings/model-aliases` handler read a different, un-hydrated copy. The handler now self-heals: when its in-memory alias map is empty it reads `settings.modelAliases` from the DB (via the existing `getSettings()` db module — no raw SQL in the route) and repopulates the map, so the UI reflects the persisted aliases on the first GET after a restart. Follow-up: the root cause is now also fixed — the `_customAliases` store in `modelDeprecation.ts` is backed by `globalThis` (key `__omniroute_customAliases__`), so the startup and app-route module graphs share **one** store and the route reads the boot-hydrated aliases directly (the DB self-heal remains as a harmless fallback), mirroring the same `globalThis` singleton pattern already applied to `thinkingBudget.ts`/`backgroundTaskDetector.ts` (#5312). Regression guards: `tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`. ([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — thanks [@jleonar2](https://github.com/jleonar2)) +- **settings(模型别名 — 重启后自愈):** 设置 → 路由页面在服务器重启后显示"未配置精确匹配别名",即使别名已持久化在数据库中。别名保存在 `modelDeprecation.ts` 的模块本地 `_customAliases` 映射中,启动路径会对其进行水合,但 Next.js 将应用路由模块图与启动图分开编译(与 #5312 相同的 webpack 块分割类),因此 `GET /api/settings/model-aliases` 处理程序读取了不同的、未水合的副本。处理程序现在可以自愈:当其内存中别名映射为空时,它从 DB 读取 `settings.modelAliases`(通过现有的 `getSettings()` db 模块 — 路由中无原始 SQL)并重新填充映射,因此 UI 在重启后的第一次 GET 上就能反映持久化的别名。后续:根本原因现也已修复 — `modelDeprecation.ts` 中的 `_customAliases` 存储由 `globalThis` 支持(键 `__omniroute_customAliases__`),因此启动和应用路由模块图共享**一个**存储,路由直接读取启动水合的别名(DB 自愈保留为无害的回退),镜像已应用于 `thinkingBudget.ts`/`backgroundTaskDetector.ts`(#5312)的相同 `globalThis` 单例模式。回归测试:`tests/unit/model-aliases-settings-route-selfheal.test.ts` + `tests/unit/model-aliases-globalthis-5777.test.ts`。([#5777](https://github.com/diegosouzapw/OmniRoute/pull/5777) — 感谢 [@jleonar2](https://github.com/jleonar2)) -- **providers (grok-cli token auto-refresh):** grok-cli OAuth tokens were never proactively refreshed before their real expiry. `mapTokens` hardcoded `expiresIn: 21600` (6 h) regardless of the token's actual lifetime, so the persisted `expiresAt` was always "now + 6 h" and the proactive `tokenHealthCheck` sweep (refresh when `expiresAt - now < 5 min`) fired 6 h after import instead of shortly before the token really expired. `mapTokens` now computes `expiresIn` from the authoritative `expires_at` field in `~/.grok/auth.json` (ISO → epoch-seconds) with a fallback to the JWT `exp` claim (payload-only decode, no signature trust); the hardcoded `21600` is kept only when neither is present. An already-expired token (real `expires_at`/`exp` in the past) is now clamped to a positive `expiresIn` via `Math.max(1, …)`, so the import route stores a near-future `expiresAt` and AutoCombo refreshes the connection instead of reading a past date and excluding it outright. Regression guards: 5 cases in `tests/unit/grok-cli-oauth.test.ts` (JWT `exp`, JSON `expires_at`, the `21600` fallback, and the two expired-token clamps). ([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — thanks [@Chewji9875](https://github.com/Chewji9875)) +- **providers(grok-cli 令牌自动刷新):** grok-cli OAuth 令牌在其实际过期之前从未被主动刷新。`mapTokens` 硬编码 `expiresIn: 21600`(6 小时),无论令牌的实际生命周期如何,因此持久化的 `expiresAt` 始终是"现在 + 6 小时",主动 `tokenHealthCheck` 清理(在 `expiresAt - now < 5 min` 时刷新)在导入后 6 小时触发,而非在令牌真正过期前不久。`mapTokens` 现在从 `~/.grok/auth.json` 中的权威 `expires_at` 字段(ISO → epoch 秒)计算 `expiresIn`,回退到 JWT `exp` 声明(仅有效载荷解码,无签名信任);硬编码的 `21600` 仅在两者都不存在时保留。已过期的令牌(实际 `expires_at`/`exp` 在过去)现在通过 `Math.max(1, …)` 被钳位为正的 `expiresIn`,因此导入路由存储一个近未来的 `expiresAt`,AutoCombo 刷新连接而非读取过去的日期并完全排除它。回归测试:`tests/unit/grok-cli-oauth.test.ts` 中 5 个用例(JWT `exp`、JSON `expires_at`、`21600` 回退,以及两个过期令牌钳位)。([#5775](https://github.com/diegosouzapw/OmniRoute/pull/5775) — 感谢 [@Chewji9875](https://github.com/Chewji9875)) -- **compression (CCR retrieve via MCP HTTP):** the `omniroute_ccr_retrieve` MCP tool returned `"CCR block not found"` for blocks stored earlier in the **same** session when called over the MCP HTTP transports (SSE / Streamable HTTP), e.g. from OpenCode in a Docker deployment. Compression stores each block keyed by the API-key principal (`String(apiKeyInfo.id)`), but the tool resolved the caller via `extra.authInfo.clientId` — which the MCP SDK never populates for API-key auth — so it fell back to `"anonymous"` and the compound store-key never matched. The retrieve tool now resolves the caller's API-key id from the MCP HTTP auth context (`httpAuthContext`) using the **same** `getApiKeyMetadata` lookup used at storage time, so retrieval matches storage. Cross-tenant IDOR isolation is preserved: a different key resolves to a different id → miss; no key → the anonymous bucket only. Regression guard: `tests/unit/compression/ccr-mcp-principal-5649.test.ts` (extraction, distinct-principal isolation, fail-closed, end-to-end store→retrieve). ([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649)) +- **compression(通过 MCP HTTP 的 CCR 检索):** `omniroute_ccr_retrieve` MCP 工具在通过 MCP HTTP 传输(SSE / Streamable HTTP)调用时,对在**同一**会话中存储的块返回 `"CCR block not found"`,例如从 Docker 部署中的 OpenCode 调用。压缩存储按 API 密钥主体键控每个块(`String(apiKeyInfo.id)`),但工具通过 `extra.authInfo.clientId` 解析调用者 — MCP SDK 在 API 密钥认证时从不填充此字段 — 因此它回退到 `"anonymous"`,复合存储键从不匹配。检索工具现在从 MCP HTTP 认证上下文(`httpAuthContext`)使用存储时所用的**相同** `getApiKeyMetadata` 查找来解析调用者的 API 密钥 ID,因此检索与存储匹配。跨租户 IDOR 隔离被保留:不同密钥解析为不同 ID → 未命中;无密钥 → 仅匿名桶。回归测试:`tests/unit/compression/ccr-mcp-principal-5649.test.ts`(提取、不同主体隔离、安全关闭、端到端存储→检索)。([#5649](https://github.com/diegosouzapw/OmniRoute/issues/5649)) -- **compression (context-editing telemetry):** streaming responses now record Context Editing savings. Anthropic surfaces `context_management.applied_edits[]` on the final `message_delta` snapshot of an SSE stream, but the streaming reconstruction (`buildStreamSummaryFromEvents` → Claude branch) dropped `context_management` entirely **and** no telemetry hook was wired into the streaming finalizer — so the delegated server-side context-clear savings (`cleared_input_tokens` / `cleared_tool_uses`) surfaced under engine `context-editing` in compression analytics **only for non-streaming responses**. The collector now preserves `context_management` from the final snapshot (last-writer-wins), and `onStreamComplete` mirrors the non-streaming `recordContextEditingTelemetryHook` (best-effort, Claude-only, HTTP 200 only). Purely additive telemetry — no payload mutation, no new env flag, no behavior change when the stream carries no `context_management`. Regression guard: `tests/unit/context-editing-streaming-telemetry.test.ts` (3). gaps v3.8.42 — T01 (5.1). +- **compression(上下文编辑遥测):** 流式响应现在记录上下文编辑节省量。Anthropic 在 SSE 流的最终 `message_delta` 快照上暴露 `context_management.applied_edits[]`,但流式重建(`buildStreamSummaryFromEvents` → Claude 分支)完全丢弃了 `context_management`,**且**流式终结器中未接入任何遥测 Hook — 因此委托的服务器端上下文清除节省量(`cleared_input_tokens` / `cleared_tool_uses`)仅在**非流式响应**中出现在压缩分析的引擎 `context-editing` 下。收集器现在从最终快照中保留 `context_management`(最后写入者获胜),`onStreamComplete` 镜像了非流式 `recordContextEditingTelemetryHook`(尽力而为,仅 Claude,仅 HTTP 200)。纯附加遥测 — 无有效载荷变更,无新环境变量标志,流不携带 `context_management` 时无行为变更。回归测试:`tests/unit/context-editing-streaming-telemetry.test.ts`(3 个)。v3.8.42 差距 — T01(5.1)。 -- **proxy (relay test diagnostics):** the Proxy Pool "Test" button showed a bare "failed" with **nothing in the server logs** when a **relay** (Vercel / Deno / Cloudflare) _responded_ with a non-200 — e.g. a `401` from an auth-token mismatch after a `STORAGE_ENCRYPTION_KEY` rotation. The relay success-path response set `success: false` but carried no `error` field, so the dashboard had no reason to show and the server logged nothing. The test now returns an actionable `error` (the HTTP status, plus an auth/encryption-key hint on `401`/`403`) and logs the failure server-side; the SOCKS5/HTTP proxy path now logs its failures too. Shaping extracted to `buildRelayTestResult` with a regression guard (`tests/unit/proxy-relay-test-error-5716.test.ts`). Note: this surfaces _why_ a relay fails — it does not repair a genuinely broken/misconfigured relay. ([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716)) +- **proxy(中继测试诊断):** 代理池"测试"按钮在**中继**(Vercel / Deno / Cloudflare)响应非 200 时显示简单的"失败",且**服务器日志中没有任何内容** — 例如 `STORAGE_ENCRYPTION_KEY` 轮换后的认证令牌不匹配导致 `401`。中继成功路径响应设置了 `success: false` 但不携带 `error` 字段,因此仪表盘没有理由显示,服务器也未记录任何内容。测试现在返回可操作的 `error`(HTTP 状态,加上对 `401`/`403` 的认证/加密密钥提示)并在服务器端记录失败;SOCKS5/HTTP 代理路径现在也记录其失败。格式提取到 `buildRelayTestResult`,带有回归测试(`tests/unit/proxy-relay-test-error-5716.test.ts`)。注意:这只是暴露中继**为什么**失败 — 不修复真正损坏/配置错误的中继。([#5716](https://github.com/diegosouzapw/OmniRoute/issues/5716)) -- **fix(dashboard):** add error boundaries for the Combos and MITM Proxy pages so a render error shows a recoverable fallback instead of a blank page. (thanks @wahyuzero) +- **fix(dashboard):** 为组合和 MITM 代理页面添加错误边界,使渲染错误显示可恢复的回退而非空白页面。(感谢 @wahyuzero) -- **providers (onboarding wizard — unsupported validation):** adding a provider whose credentials have **no live validator** (LMArena, PiAPI, …) failed silently in the Add-Provider wizard. The `/api/providers/validate` endpoint returns `HTTP 400 + { unsupported: true }` for these (#5565/#5567), but the wizard's `validateOnboardingApiKey` ran it through `expectOk`, which threw on the non-200 — so the flow jumped to the error step and the connection was **never created**. The wizard now treats `unsupported: true` as a non-blocking "can't verify" and proceeds to save, mirroring `AddApiKeyModal`. Regression guard added to `tests/unit/provider-onboarding-wizard.test.ts`. (related to [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692)) +- **providers(入门向导 — 不支持的验证):** 添加凭据没有**实时验证器**的提供者(LMArena、PiAPI 等)在添加提供者向导中静默失败。`/api/providers/validate` 端点对这些返回 `HTTP 400 + { unsupported: true }`(#5565/#5567),但向导的 `validateOnboardingApiKey` 通过 `expectOk` 运行它,后者在非 200 时抛出异常 — 因此流程跳转到错误步骤,连接**从未被创建**。向导现在将 `unsupported: true` 视为非阻塞的"无法验证",并继续保存,镜像 `AddApiKeyModal`。回归测试添加到 `tests/unit/provider-onboarding-wizard.test.ts`。(相关于 [#5692](https://github.com/diegosouzapw/OmniRoute/issues/5692)) -- **dashboard (Quick Start step 1):** the Quick Start "Create API key" step told users to "Go to **Endpoint** → Registered Keys" and linked to `/dashboard/endpoint`, but API keys are created on the **API Manager** page (`/dashboard/api-manager`, sidebar "API Keys") — the Endpoint page has no "Registered Keys" section, so users followed the link and could not find where to create a key. Step 1 now reads "Go to **API Keys**" and links to `/dashboard/api-manager`. Regression guard: `tests/unit/ui/quick-start-api-keys-link-5695.test.ts`. ([#5695](https://github.com/diegosouzapw/OmniRoute/issues/5695)) +- **dashboard(快速入门步骤 1):** 快速入门"创建 API 密钥"步骤告诉用户"前往**端点** → 已注册密钥"并链接到 `/dashboard/endpoint`,但 API 密钥是在 **API 管理**页面(`/dashboard/api-manager`,侧边栏"API 密钥")创建的 — 端点页面没有"已注册密钥"部分,因此用户跟随链接后找不到在哪里创建密钥。步骤 1 现在显示"前往 **API 密钥**"并链接到 `/dashboard/api-manager`。回归测试:`tests/unit/ui/quick-start-api-keys-link-5695.test.ts`。([#5695](https://github.com/diegosouzapw/OmniRoute/issues/5695)) -- **providers (DashScope/Alibaba setup link):** the "Get API key" link for the **Alibaba** and **Alibaba (China)** providers pointed at the bare API host (`dashscope-intl.aliyuncs.com` / `dashscope.aliyuncs.com`), which returns **404** in a browser — API hostnames have no homepage. Repointed to the consoles where keys are actually issued: `bailian.console.alibabacloud.com` (international) and `dashscope.console.aliyun.com` (China). Same class as #5572/#5574/#5576; regression guard added to `tests/unit/provider-setup-links-5572.test.ts`. ([#5665](https://github.com/diegosouzapw/OmniRoute/issues/5665)) +- **providers(DashScope/Alibaba 设置链接):** **Alibaba** 和 **Alibaba (China)** 提供者的"获取 API 密钥"链接指向裸 API 主机(`dashscope-intl.aliyuncs.com` / `dashscope.aliyuncs.com`),在浏览器中返回 **404** — API 主机名没有主页。重新指向实际发放密钥的控制台:`bailian.console.alibabacloud.com`(国际)和 `dashscope.console.aliyun.com`(中国)。与 #5572/#5574/#5576 相同类别;回归测试添加到 `tests/unit/provider-setup-links-5572.test.ts`。([#5665](https://github.com/diegosouzapw/OmniRoute/issues/5665)) -- **thinking / runtime-config (module-graph fix):** operator-configured proxy settings that are hydrated at **boot** but read **per-request** were silently ignored in production. Next.js compiles `instrumentation.ts` (boot hydration via `applyRuntimeSettings` / restore hooks) as a **separate webpack module graph** from the app-route / open-sse executors, so a module-local `let _config` singleton is **duplicated** — the boot copy is hydrated but the request path reads a different, un-hydrated copy. Live VPS validation proved the Thinking-Budget hydration ran to completion at boot yet `base.ts` still saw the `passthrough` default (this is why #5312 fix A stayed broken even after the boot-wiring fix). Fixed by backing the singletons with `globalThis` (the pattern `systemPrompt.ts` already uses for the Global System Prompt, #2470), so all module-graph copies share one instance: **`thinkingBudget.ts`** (the dashboard Thinking-Budget mode now reaches the executor), **`backgroundTaskDetector.ts`** (the opt-in background-model degradation now actually fires on requests), and **`systemTransforms.ts`** (operator pipeline overrides now reach the request path). `payloadRules.ts` was already safe (it lazily self-loads from the DB per request, #2986). Regression guards: `tests/unit/thinking-budget-globalthis-5312.test.ts` + `tests/unit/runtime-config-globalthis-5312.test.ts` (assert globalThis-backed sharing; a module-local `let` fails them). ([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312)) +- **thinking / runtime-config(模块图修复):** 在启动时水合但**每请求**读取的操作者配置代理设置在生产中静默地被忽略。Next.js 将 `instrumentation.ts`(通过 `applyRuntimeSettings` / 恢复 Hook 进行启动水合)编译为与 app-route / open-sse executor **分离的 webpack 模块图**,因此模块本地的 `let _config` 单例被**复制** — 启动副本已水合,但请求路径读取了不同的、未水合的副本。实时 VPS 验证证明 Thinking-Budget 水合在启动时完成,但 `base.ts` 仍然看到 `passthrough` 默认值(这就是 #5312 修复 A 在启动连线修复后仍然失效的原因)。通过用 `globalThis` 支持单例来修复(`systemPrompt.ts` 已用于全局系统提示的模式,#2470),因此所有模块图副本共享一个实例:**`thinkingBudget.ts`**(仪表盘 Thinking-Budget 模式现在到达 executor)、**`backgroundTaskDetector.ts`**(可选后台模型降级现在实际在请求时触发)、**`systemTransforms.ts`**(操作者管道覆盖现在到达请求路径)。`payloadRules.ts` 已安全(它每请求从 DB 惰性加载自身,#2986)。回归测试:`tests/unit/thinking-budget-globalthis-5312.test.ts` + `tests/unit/runtime-config-globalthis-5312.test.ts`(断言 globalThis 支持的共享;模块本地的 `let` 会失败)。([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312)) -- **thinking (Claude OAuth):** restore the proxy-level **Thinking-Budget** config on startup. The dashboard mode (`auto`/`custom`/`adaptive`) is persisted under `settings.thinkingBudget`, but the boot-time hydration (`hydrateThinkingBudgetConfig`) was only wired into `src/server-init.ts` — an **unused module that never runs in production** — so the operator's choice silently reverted to the `passthrough` default on every restart (#5312 fix A was non-functional, even though its direct unit test passed). The hydration now runs in the real boot path (`src/instrumentation-node.ts`), alongside the Global System Prompt restore. Surfaced by live Anthropic-OAuth validation on the VPS. Regression guard: `tests/unit/thinking-budget-boot-wiring-5312.test.ts` (asserts the production boot module calls the hydration, not just the function in isolation). ([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312)) +- **thinking(Claude OAuth):** 在启动时恢复代理级 **Thinking-Budget** 配置。仪表盘模式(`auto`/`custom`/`adaptive`)持久化在 `settings.thinkingBudget` 下,但启动时水合(`hydrateThinkingBudgetConfig`)仅接入 `src/server-init.ts` — 一个**生产中从未运行的未使用模块** — 因此操作者的选择在每次重启时静默回退到 `passthrough` 默认值(#5312 修复 A 无效,尽管其直接单元测试通过)。水合现在在实际启动路径(`src/instrumentation-node.ts`)中运行,与全局系统提示恢复并列。由 VPS 上的实时 Anthropic-OAuth 验证发现。回归测试:`tests/unit/thinking-budget-boot-wiring-5312.test.ts`(断言生产启动模块调用水合,而非仅在隔离中测试函数)。([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312)) -- **translator/chatcore (hardening):** re-apply two defensive review-fixes that were dropped in a branch rebuild before #5661 / #5662 landed. (1) `mergeConsecutiveSameRoleContents` (OpenAI→Gemini) now shallow-copies each entry and its `parts` array instead of pushing the input reference, so the consecutive-same-role merge never mutates the caller's objects. (2) `defaultClaudeToolType` (Claude tool defaults) now passes any non-object array entry (`null` / primitive) through unchanged instead of spreading it into a fabricated `{ type: "custom", … }` tool. No behavior change on real payloads (Gemini contents are freshly built; Claude tools are always objects); both properties are now locked by regression tests in `tests/unit/translator-gemini-consecutive-role-2191.test.ts` and `tests/unit/claude-tool-type-default-2195.test.ts`. +- **translator/chatcore(加固):** 重新应用在 #5661 / #5662 之前分支重建中被丢弃的两个防御性审查修复。(1) `mergeConsecutiveSameRoleContents`(OpenAI→Gemini)现在浅拷贝每个条目及其 `parts` 数组,而非推送输入引用,因此连续相同角色合并永远不会改变调用者的对象。(2) `defaultClaudeToolType`(Claude 工具默认值)现在将任何非对象数组条目(`null` / 原语)原样传递,而非将其展开为虚构的 `{ type: "custom", … }` 工具。在真实负载上无行为变更(Gemini 内容是新构建的;Claude 工具始终是对象);两个属性现在由 `tests/unit/translator-gemini-consecutive-role-2191.test.ts` 和 `tests/unit/claude-tool-type-default-2195.test.ts` 中的回归测试锁定。 -- **providers (grok-cli):** truncate the tool list when it exceeds a provider's hard limit, so grok-cli (`cli-chat-proxy.grok.com`, max 200 tools) no longer rejects requests with `Maximum tools limit reached`. Adds a proactive `PROVIDER_TOOL_LIMITS` map (`grok-cli: 200`, consulted before the reactive cache), a corrected limit-parsing regex that captures the stated maximum (`200`) instead of the supplied count (`427`), and removes the broken `< MAX_TOOLS_LIMIT` truncation gate so truncation now fires whenever `tools.length` exceeds the effective limit. Regression guard: `tests/unit/tool-limit-detector.test.ts`. ([#5563](https://github.com/diegosouzapw/OmniRoute/pull/5563) — thanks @Chewji9875) +- **providers(grok-cli):** 当工具列表超过提供者的硬限制时截断工具列表,使 grok-cli(`cli-chat-proxy.grok.com`,最大 200 个工具)不再以 `Maximum tools limit reached` 拒绝请求。新增主动 `PROVIDER_TOOL_LIMITS` 映射(`grok-cli: 200`,在反应式缓存之前查询),修正的限制解析正则表达式捕获声明的最大值(`200`)而非提供的数量(`427`),并移除损坏的 `< MAX_TOOLS_LIMIT` 截断门,因此截断现在在 `tools.length` 超过有效限制时触发。回归测试:`tests/unit/tool-limit-detector.test.ts`。([#5563](https://github.com/diegosouzapw/OmniRoute/pull/5563) — 感谢 @Chewji9875) -- **resilience (antigravity):** record model lockout for Antigravity `429 rate_limit_exceeded` errors. Antigravity's `"Resource has been exhausted (e.g. check quota)."` text was matched by overly broad `QUOTA_PATTERNS` and misclassified as `QUOTA_EXHAUSTED`, so the combo retry path was skipped (`providerExhausted`) and the model was never cooled down. Classification now prefers the structured error code — `classifyErrorText(structuredError?.code || errorText)` — so a `rate_limit_exceeded` code is treated as a transient rate-limit (not quota), and the two broad patterns (`/resource.*exhaust/i`, `/check.*quota/i`) were replaced with Antigravity-specific ones (`individual quota reached`, `enable overages`). ([#5579](https://github.com/diegosouzapw/OmniRoute/pull/5579) — thanks @Chewji9875) +- **resilience(antigravity):** 为 Antigravity `429 rate_limit_exceeded` 错误记录模型锁定。Antigravity 的 `"Resource has been exhausted (e.g. check quota)."` 文本被过于宽泛的 `QUOTA_PATTERNS` 匹配,错误分类为 `QUOTA_EXHAUSTED`,因此组合重试路径被跳过(`providerExhausted`),模型从未被冷却。分类现在优先使用结构化错误代码 — `classifyErrorText(structuredError?.code || errorText)` — 因此 `rate_limit_exceeded` 代码被视为瞬态速率限制(非配额),两个宽泛模式(`/resource.*exhaust/i`、`/check.*quota/i`)替换为 Antigravity 特定的模式(`individual quota reached`、`enable overages`)。([#5579](https://github.com/diegosouzapw/OmniRoute/pull/5579) — 感谢 @Chewji9875) -- **providers (OpenAI-compatible):** Codex MCP / `tool_search` deferred discovery (and `apply_patch`) now works through a **Custom OpenAI-compatible provider**. When such a provider received a Responses-API-shaped request that carried MCP / `tool_search` tools, OmniRoute downgraded it to `/chat/completions`, which drops the deferred tool-discovery mechanism — so the MCP namespaces never surfaced to the model and `apply_patch` was mis-handled as a JSON tool. The executor now detects a Responses-shaped request (`input` / `previous_response_id` / `max_output_tokens` / `reasoning`) that carries `namespace` / `tool_search*` tools and routes it to the upstream `/responses` endpoint natively instead of downgrading (it can also be forced via `providerSpecificData._omnirouteForceResponsesUpstream`). This is a distinct code path from the official Codex OAuth backend (#3033 / #4539, which the earlier fix never touched). Regression guard: `tests/unit/executor-default-base.test.ts`. Thanks to @KooshaPari for the fix. ([#5483](https://github.com/diegosouzapw/OmniRoute/issues/5483)) +- **providers(OpenAI-compatible):** Codex MCP / `tool_search` 延迟发现(及 `apply_patch`)现在通过**自定义 OpenAI-compatible 提供者**工作。当此类提供者收到携带 MCP / `tool_search` 工具的 Responses-API 格式请求时,OmniRoute 将其降级为 `/chat/completions`,这会丢弃延迟工具发现机制 — 因此 MCP 命名空间从未暴露给模型,`apply_patch` 被错误地处理为 JSON 工具。executor 现在检测携带 `namespace` / `tool_search*` 工具的 Responses 格式请求(`input` / `previous_response_id` / `max_output_tokens` / `reasoning`),并将其原生路由到上游 `/responses` 端点而非降级(也可通过 `providerSpecificData._omnirouteForceResponsesUpstream` 强制)。这是与官方 Codex OAuth 后端(#3033 / #4539,早期修复从未触及)不同的代码路径。回归测试:`tests/unit/executor-default-base.test.ts`。感谢 @KooshaPari 的修复。([#5483](https://github.com/diegosouzapw/OmniRoute/issues/5483)) -- **dashboard (routing):** selecting the **fusion** strategy on the Global Routing defaults tab now reveals fusion-specific config instead of only the generic resilience fields. Fusion's engine knobs — `judgeModel` (the model that synthesizes the panel answers) and `fusionTuning` (`minPanel` / `stragglerGraceMs` / `panelHardTimeoutMs`) — already existed in the schema and the per-combo editor, but the Global Routing tab never surfaced them, so picking "fusion" there was effectively a no-op. The fields are now shown (extracted into a new `FusionDefaultsFields` component). Voting / aggregation-mode / per-provider-weight are intentionally not shown — those don't exist in the fusion engine. Regression guard: `tests/unit/ui/combo-defaults-fusion-5598.test.tsx`. ([#5598](https://github.com/diegosouzapw/OmniRoute/issues/5598)) +- **dashboard(路由):** 在全局路由默认选项卡上选择 **fusion** 策略现在显示 fusion 特定配置,而非仅显示通用弹性字段。Fusion 的引擎旋钮 — `judgeModel`(综合面板答案的模型)和 `fusionTuning`(`minPanel` / `stragglerGraceMs` / `panelHardTimeoutMs`)— 已存在于模式和每个组合编辑器中,但全局路由选项卡从未暴露它们,因此在那里选择 "fusion" 实际上是无操作的。这些字段现在被显示(提取到新的 `FusionDefaultsFields` 组件)。投票/聚合模式/每提供者权重有意不显示 — 这些在 fusion 引擎中不存在。回归测试:`tests/unit/ui/combo-defaults-fusion-5598.test.tsx`。([#5598](https://github.com/diegosouzapw/OmniRoute/issues/5598)) -- **dashboard (free proxy pool):** the free proxy pool "Sync All" no longer fails silently with `Total: 0`. Three fixes: (1) the **IPLocate** source fetched `…/protocols/.json` and parsed it as JSON, but the upstream list is plain text (`.txt`, one `ip:port` per line) — every protocol 404'd / failed to parse; it now fetches `.txt` and parses the line list. (2) The sync route **isolates each source** in its own try/catch, so one provider throwing (e.g. a TLS handshake failure) no longer aborts the whole sync — the working sources still populate the pool. (3) The UI now **surfaces the per-source errors** the route already returns, instead of discarding the response, so a partial/empty sync explains itself. Regression guards: `tests/unit/free-proxy-providers.test.ts`, `tests/unit/proxy-pool-sync-4878.test.ts`, `tests/unit/free-pool-tab.test.tsx`. ([#5595](https://github.com/diegosouzapw/OmniRoute/issues/5595)) +- **dashboard(免费代理池):** 免费代理池"全部同步"不再静默失败,显示 `Total: 0`。三个修复:(1) **IPLocate** 源获取 `…/protocols/.json` 并将其解析为 JSON,但上游列表是纯文本(`.txt`,每行一个 `ip:port`)— 每个协议都 404/解析失败;现在获取 `.txt` 并解析行列表。(2) 同步路由**将每个源隔离**在各自的 try/catch 中,因此一个提供者抛出异常(例如 TLS 握手失败)不再中止整个同步 — 工作的源仍填充池。(3) UI 现在**显示路由已返回的每源错误**,而非丢弃响应,因此部分/空同步可以解释自身。回归测试:`tests/unit/free-proxy-providers.test.ts`、`tests/unit/proxy-pool-sync-4878.test.ts`、`tests/unit/free-pool-tab.test.tsx`。([#5595](https://github.com/diegosouzapw/OmniRoute/issues/5595)) -- **dashboard (memory engine):** the memory engine status page no longer mixes English and Portuguese. The embedding / vector-store / rerank **status detail strings** were hardcoded in Portuguese in the backend (`resolveEmbeddingSource`, `engineStatus`), e.g. `auto: nenhuma fonte de embedding disponível` and `sqlite-vec ativo, dim=…`, while the surrounding UI labels render from the English i18n bundle — so an English user saw a half-translated page. The backend detail strings are now English (`auto: no embedding source available`, `sqlite-vec active, dim=…`, etc.), matching the rest of the page. Regression guard: `tests/unit/memory-engine-status.test.ts`. ([#5596](https://github.com/diegosouzapw/OmniRoute/issues/5596)) +- **dashboard(记忆引擎):** 记忆引擎状态页面不再混合英文和葡萄牙语。嵌入/向量存储/重排序**状态详情字符串**在后端被硬编码为葡萄牙语(`resolveEmbeddingSource`、`engineStatus`),例如 `auto: nenhuma fonte de embedding disponível` 和 `sqlite-vec ativo, dim=…`,而周围的 UI 标签从英文 i18n 包渲染 — 因此英文用户看到的是半翻译页面。后端详情字符串现在是英文(`auto: no embedding source available`、`sqlite-vec active, dim=…` 等),与页面其余部分匹配。回归测试:`tests/unit/memory-engine-status.test.ts`。([#5596](https://github.com/diegosouzapw/OmniRoute/issues/5596)) -- **providers (cline):** stop falsely mapping valid **Cline** (OAuth) responses to `502 empty_choices` + account cooldown. `detectMalformedNonStream` only recognized `choices[].message.content` as a **string**, but some OpenAI-compatible upstreams — Cline via OAuth among them — return `content` as an **array of Anthropic-style text blocks** inside an OpenAI envelope. A non-empty response (recvBytes > 0) was therefore classified as `empty_choices` and turned into a 502 that also cooled the account down. The malformed-response detector now also treats a content array carrying at least one non-empty `text` block as real output. Regression guard: `tests/unit/diagnostics.test.ts`. ([#5559](https://github.com/diegosouzapw/OmniRoute/issues/5559)) +- **providers(cline):** 停止将有效的 **Cline**(OAuth)响应错误映射为 `502 empty_choices` + 账户冷却。`detectMalformedNonStream` 仅识别 `choices[].message.content` 为**字符串**,但一些 OpenAI-compatible 上游 — 通过 OAuth 的 Cline 等 — 在 OpenAI 信封内返回 `content` 作为 **Anthropic 风格文本块数组**。因此非空响应(recvBytes > 0)被分类为 `empty_choices` 并转换为 502,还冷却了账户。格式错误的响应检测器现在也将携带至少一个非空 `text` 块的 content 数组视为真实输出。回归测试:`tests/unit/diagnostics.test.ts`。([#5559](https://github.com/diegosouzapw/OmniRoute/issues/5559)) -- **embedded services (Windows):** fix **CLIProxyAPI install failing instantly with `spawn unzip ENOENT`** on Windows. The binary extractor spawned `unzip`, which is not a Windows system command — it only ships inside Git for Windows' `usr/bin`, a directory Node's `spawn` PATH never sees, so even users with Git installed hit the error. On Windows the extractor now uses PowerShell's built-in `Expand-Archive` (via `execFileAsync`, no shell — paths pass as a single non-interpreted arg, with `''`-escaping + `-LiteralPath` as defense in depth); other platforms keep using `unzip`. This is distinct from #5379 (that was `npm.cmd` needing `shell: true`). Regression guard: `tests/unit/binary-manager-extract-zip-5590.test.ts`. ([#5590](https://github.com/diegosouzapw/OmniRoute/issues/5590)) +- **embedded services(Windows):** 修复 **CLIProxyAPI 在 Windows 上立即失败并报 `spawn unzip ENOENT`**。二进制提取器生成了 `unzip`,这不是 Windows 系统命令 — 它仅在 Git for Windows 的 `usr/bin` 中提供,这是 Node 的 `spawn` PATH 永远不会看到的目录,因此即使安装了 Git 的用户也会遇到错误。在 Windows 上,提取器现在使用 PowerShell 内置的 `Expand-Archive`(通过 `execFileAsync`,无 shell — 路径作为单个非解释参数传递,带有 `''`-转义 + `-LiteralPath` 作为纵深防御);其他平台继续使用 `unzip`。这与 #5379 不同(那是 `npm.cmd` 需要 `shell: true`)。回归测试:`tests/unit/binary-manager-extract-zip-5590.test.ts`。([#5590](https://github.com/diegosouzapw/OmniRoute/issues/5590)) -- **storage (daemon):** fix a Node.js **out-of-memory crash on startup** when `storage.sqlite` grows large (~170 MB+). The boot-time call-log cleanup (`cleanupExpiredLogs` → `rotateCallLogs`) ran two **unbounded `SELECT … FROM call_logs … .all()`** queries — `listReferencedArtifacts` (every artifact path) and `deleteCallLogsBefore` (every id before the retention cutoff). `node:sqlite`'s `StatementSync.all()` materializes the entire result set as JS objects at once, so on a large table the V8 heap blew up and the process crashed before binding (`FATAL ERROR: … heap out of memory`, native frame `node::sqlite::StatementSync::All`). Both queries now page through `call_logs` in bounded 5 000-row chunks (new `src/lib/usage/callLogsBoundedQueries.ts`), keeping peak memory flat regardless of table size — no more manual `--max-old-space-size` bump required. Regression guard: `tests/unit/call-log-oom-unbounded-5618.test.ts`. ([#5618](https://github.com/diegosouzapw/OmniRoute/issues/5618)) +- **storage(守护进程):** 修复当 `storage.sqlite` 变得很大(~170 MB+)时启动时的 Node.js **内存不足崩溃**。启动时调用日志清理(`cleanupExpiredLogs` → `rotateCallLogs`)运行了两个**无界 `SELECT … FROM call_logs … .all()`** 查询 — `listReferencedArtifacts`(每个制品路径)和 `deleteCallLogsBefore`(保留截止日期之前的每个 id)。`node:sqlite` 的 `StatementSync.all()` 一次性将整个结果集物化为 JS 对象,因此在大型表上 V8 堆爆了,进程在绑定前崩溃(`FATAL ERROR: … heap out of memory`,原生帧 `node::sqlite::StatementSync::All`)。两个查询现在以有界 5,000 行块分页遍历 `call_logs`(新增 `src/lib/usage/callLogsBoundedQueries.ts`),保持峰值内存不变,无论表大小如何 — 不再需要手动 `--max-old-space-size` 提升。回归测试:`tests/unit/call-log-oom-unbounded-5618.test.ts`。([#5618](https://github.com/diegosouzapw/OmniRoute/issues/5618)) -- **dashboard (provider setup):** fix three provider setup links that pointed at 404 pages. **Ollama Cloud** / **ollama-search** linked to `ollama.com/settings/api-keys` → corrected to `ollama.com/settings/keys` (the page moved; Ollama Cloud is a real keyed service, so the field stays). **SearchAPI** linked to the bare `searchapi.io/docs` (404) → `searchapi.io/docs/google`. **You.com** linked to `you.com/docs/search/overview` (404) → `you.com/business/api/` (the developer portal). All three replacements were verified live. Regression guard: `tests/unit/provider-setup-links-5572.test.ts`. ([#5572](https://github.com/diegosouzapw/OmniRoute/issues/5572), [#5574](https://github.com/diegosouzapw/OmniRoute/issues/5574), [#5576](https://github.com/diegosouzapw/OmniRoute/issues/5576)) +- **dashboard(提供者设置):** 修复三个指向 404 页面的提供者设置链接。**Ollama Cloud** / **ollama-search** 链接到 `ollama.com/settings/api-keys` → 更正为 `ollama.com/settings/keys`(页面已移动;Ollama Cloud 是真实的密钥服务,因此字段保留)。**SearchAPI** 链接到裸 `searchapi.io/docs`(404)→ `searchapi.io/docs/google`。**You.com** 链接到 `you.com/docs/search/overview`(404)→ `you.com/business/api/`(开发者门户)。所有三个替换均经过实时验证。回归测试:`tests/unit/provider-setup-links-5572.test.ts`。([#5572](https://github.com/diegosouzapw/OmniRoute/issues/5572)、[#5574](https://github.com/diegosouzapw/OmniRoute/issues/5574)、[#5576](https://github.com/diegosouzapw/OmniRoute/issues/5576)) -- **providers (AI/ML API):** the model-import step now loads the **live** AI/ML API catalog (400+ models) instead of falling back to a stale 6-model seed. The registry had no `modelsUrl`, so the route silently used the bundled catalog with an "API unavailable — using local catalog" warning even when the key was valid. AI/ML API exposes its full catalog at the public, **auth-free** `https://api.aimlapi.com/models` endpoint (a bare array of `{ id, type, info }`, distinct from the OpenAI-compat `/v1/models`); it's now wired into the models route's discovery config, with the bundled catalog kept as the offline fallback. Regression guard: `tests/unit/provider-models-route.test.ts`. ([#5570](https://github.com/diegosouzapw/OmniRoute/issues/5570)) +- **providers(AI/ML API):** 模型导入步骤现在加载**实时** AI/ML API 目录(400+ 模型),而非回退到过时的 6 模型种子。注册表没有 `modelsUrl`,因此路由静默使用打包目录,即使密钥有效也显示"API 不可用 — 使用本地目录"警告。AI/ML API 在公开、**无需认证**的 `https://api.aimlapi.com/models` 端点暴露其完整目录(裸 `{ id, type, info }` 数组,与 OpenAI-compat `/v1/models` 不同);现在已接入模型路由的发现配置,打包目录保留为离线回退。回归测试:`tests/unit/provider-models-route.test.ts`。([#5570](https://github.com/diegosouzapw/OmniRoute/issues/5570)) -- **providers (CablyAI):** mark **CablyAI** deprecated — `cablyai.com` no longer resolves (DNS `NXDOMAIN`, verified 2026-06-30); the domain is gone. The provider is removed from the models-route discovery config so the import step returns a clean error instead of an **unhandled 500 crash** (the dead-domain fetch threw with no local-catalog fallback), and the registry entry now carries `deprecated: true` / `riskNoticeVariant: "deprecated"` so the dashboard flags existing connections (same treatment as the shut-down `glhf`/`kluster.ai` gateways). Regression guard: `tests/unit/provider-models-route.test.ts`. ([#5568](https://github.com/diegosouzapw/OmniRoute/issues/5568)) +- **providers(CablyAI):** 将 **CablyAI** 标记为已弃用 — `cablyai.com` 不再解析(DNS `NXDOMAIN`,2026-06-30 验证);域名已消失。提供者从模型路由发现配置中移除,因此导入步骤返回干净错误而非**未处理的 500 崩溃**(死域名 fetch 抛出异常,无本地目录回退),注册表条目现在携带 `deprecated: true` / `riskNoticeVariant: "deprecated"`,使仪表盘标记现有连接(与已关闭的 `glhf`/`kluster.ai` 网关相同处理)。回归测试:`tests/unit/provider-models-route.test.ts`。([#5568](https://github.com/diegosouzapw/OmniRoute/issues/5568)) -- **dashboard (provider add):** non-LLM search/agent providers no longer fail the model-import step with a red `Provider does not support models listing`. **Jules** (Google Labs coding agent), **linkup-search** (Linkup web search), **ollama-search** (Ollama Cloud web search — distinct from the local Ollama LLM), and **searchapi-search** (SearchAPI SERP) have no `/v1/models` endpoint, so the import surfaced a failure for expected behavior. Each now ships a small static catalog of its selectable capability ids — Linkup's `fast`/`standard`/`deep` search depths, SearchAPI's `google`/`bing`/`youtube`/… engines, a single Jules/Ollama-web-search entry — so the import step returns a usable list (`source: local_catalog`) instead of an error. Regression guard: `tests/unit/provider-models-route.test.ts`. ([#5569](https://github.com/diegosouzapw/OmniRoute/issues/5569), [#5571](https://github.com/diegosouzapw/OmniRoute/issues/5571), [#5573](https://github.com/diegosouzapw/OmniRoute/issues/5573), [#5575](https://github.com/diegosouzapw/OmniRoute/issues/5575)) +- **dashboard(添加提供者):** 非 LLM 搜索/代理提供者不再在模型导入步骤中失败,显示红色 `Provider does not support models listing`。**Jules**(Google Labs 编码代理)、**linkup-search**(Linkup 网络搜索)、**ollama-search**(Ollama Cloud 网络搜索 — 不同于本地 Ollama LLM)和 **searchapi-search**(SearchAPI SERP)没有 `/v1/models` 端点,因此导入将预期行为显示为失败。每个现在提供其可选能力 ID 的小型静态目录 — Linkup 的 `fast`/`standard`/`deep` 搜索深度,SearchAPI 的 `google`/`bing`/`youtube`/… 引擎,单个 Jules/Ollama-web-search 条目 — 因此导入步骤返回可用列表(`source: local_catalog`)而非错误。回归测试:`tests/unit/provider-models-route.test.ts`。([#5569](https://github.com/diegosouzapw/OmniRoute/issues/5569)、[#5571](https://github.com/diegosouzapw/OmniRoute/issues/5571)、[#5573](https://github.com/diegosouzapw/OmniRoute/issues/5573)、[#5575](https://github.com/diegosouzapw/OmniRoute/issues/5575)) -- **dashboard (provider add):** providers without a live key/cookie validator (e.g. **LMArena (Free)**, **PiAPI**) can now be saved. The Add-connection modal treated the backend's `"Provider validation not supported"` response as a hard **Invalid** state and blocked Save entirely, leaving those providers impossible to add. The validate route now returns `unsupported: true` alongside the message, and the modal treats that as a non-blocking warning — the "Check" badge still shows "validation not supported" (informational), but Save persists the credential as-is. Regression guards: `tests/unit/ui/add-api-key-modal-unsupported-save-5565.test.tsx` (Save proceeds) and `tests/unit/providers-validate-route.test.ts` (wire-format). ([#5565](https://github.com/diegosouzapw/OmniRoute/issues/5565), [#5567](https://github.com/diegosouzapw/OmniRoute/issues/5567)) +- **dashboard(添加提供者):** 没有实时密钥/cookie 验证器的提供者(例如 **LMArena (Free)**、**PiAPI**)现在可以保存。添加连接模态框将后端的 `"Provider validation not supported"` 响应视为硬**无效**状态,完全阻止保存,使这些提供者无法添加。验证路由现在在消息旁边返回 `unsupported: true`,模态框将其视为非阻塞警告 — "检查"徽章仍显示"验证不支持"(信息性),但保存将凭据原样持久化。回归测试:`tests/unit/ui/add-api-key-modal-unsupported-save-5565.test.tsx`(保存继续)和 `tests/unit/providers-validate-route.test.ts`(线路格式)。([#5565](https://github.com/diegosouzapw/OmniRoute/issues/5565)、[#5567](https://github.com/diegosouzapw/OmniRoute/issues/5567)) -- **providers (codex):** fix the **Codex Responses WebSocket** path (`/v1/responses`), which regressed in v3.8.40 with a client-visible `Invalid JSON body` and bypassed the configured proxy. (1) #5591 — PR #5237 bumped the impersonation TLS profile to `chrome_149`, but `wreq-js@2.3.1` only supports up to `chrome_147`; the unknown profile produced a degenerate fingerprint and ChatGPT rejected the upstream upgrade. The Codex WS path is reverted to the proven `chrome_142` (the v3.8.39 value), and the over-bumped `grok-web`/`claude-web` profiles (masked by their circuit-breaker but silently dropping TLS impersonation) are restored to `chrome_146`. A new regression guard asserts every configured `chrome_*` profile exists in the installed `wreq-js` typings (`tests/unit/tls-profiles-valid-5591.test.mjs`). (2) #5611 — the upstream `wreq-js.websocket()` connect ignored the Proxy Registry, so a no-direct-egress Docker container failed with a DNS error; the prepare route now resolves the Global/provider proxy and threads it through to the WS connect. Regression guard in `tests/unit/responses-ws-proxy.test.mjs`. ([#5591](https://github.com/diegosouzapw/OmniRoute/issues/5591), [#5611](https://github.com/diegosouzapw/OmniRoute/issues/5611)) +- **providers(codex):** 修复 **Codex Responses WebSocket** 路径(`/v1/responses`),在 v3.8.40 中回归,出现客户端可见的 `Invalid JSON body` 并绕过配置的代理。(1) #5591 — PR #5237 将模拟 TLS 配置文件提升到 `chrome_149`,但 `wreq-js@2.3.1` 仅支持到 `chrome_147`;未知配置文件产生退化指纹,ChatGPT 拒绝上游升级。Codex WS 路径回退到经过验证的 `chrome_142`(v3.8.39 的值),过度提升的 `grok-web`/`claude-web` 配置文件(被其断路器掩盖但静默丢弃 TLS 模拟)恢复为 `chrome_146`。新增回归测试断言每个配置的 `chrome_*` 配置文件存在于已安装的 `wreq-js` 类型中(`tests/unit/tls-profiles-valid-5591.test.mjs`)。(2) #5611 — 上游 `wreq-js.websocket()` 连接忽略代理注册表,因此无直接出口的 Docker 容器以 DNS 错误失败;准备路由现在解析全局/提供者代理并将其线程传递到 WS 连接。回归测试在 `tests/unit/responses-ws-proxy.test.mjs` 中。([#5591](https://github.com/diegosouzapw/OmniRoute/issues/5591)、[#5611](https://github.com/diegosouzapw/OmniRoute/issues/5611)) -- **providers (GLM):** GLM **5.1 / 5.2** now keep the `system` role instead of having the system prompt folded into the first user turn. `roleNormalizer.ts` matched every `glm*` id with a blanket `startsWith("glm")` / `startsWith("glm-")` prefix, so the next-generation models — which z.ai documents as supporting the `system` role (GLM > 5.0) — were normalized as if they rejected it, degrading instruction-following. The matcher is now version-aware: it strips the system role only for bare `glm`, the 4.x family, and the 5.0 generation, and preserves it for `glm-5.1`/`glm-5.2` (and the Fireworks `glm-5p1` point alias). The ZenMux vendor-prefixed `z-ai/glm-*` compressed-history rule and the ERNIE rule are unchanged. Regression guards in `tests/unit/role-normalizer.test.ts`. ([#5610](https://github.com/diegosouzapw/OmniRoute/issues/5610)) +- **providers(GLM):** GLM **5.1 / 5.2** 现在保留 `system` 角色,而非将系统提示折叠到第一个用户轮次中。`roleNormalizer.ts` 用 `startsWith("glm")` / `startsWith("glm-")` 前缀匹配每个 `glm*` ID,因此下一代模型 — z.ai 文档化支持 `system` 角色(GLM > 5.0)— 被规范化为好像拒绝它一样,降低了指令遵循能力。匹配器现在是版本感知的:仅对裸 `glm`、4.x 系列和 5.0 代剥离系统角色,为 `glm-5.1`/`glm-5.2`(及 Fireworks `glm-5p1` 点别名)保留它。ZenMux 供应商前缀 `z-ai/glm-*` 压缩历史规则和 ERNIE 规则不变。回归测试在 `tests/unit/role-normalizer.test.ts` 中。([#5610](https://github.com/diegosouzapw/OmniRoute/issues/5610)) -- **Security hardening follow-ups (v3.8.15):** the `auth_token` cookie now sets an explicit 30-day `maxAge` so sessions persist as intended (Seg3); the management bootstrap warns at boot when `INITIAL_PASSWORD` is left at the insecure `CHANGEME` default (Seg2); VS Code path-token endpoints (`/api/v1/vscode/raw/[token]`) emit a once-per-process security warning since the API key travels in the URL and can leak via logs/proxies (Seg4); the system version route resolves the real global install path via `npm root -g` instead of a hardcoded `/app` (Bug3); and auto-update mode detection segment-matches `node_modules` instead of substring-matching, eliminating false "global install" positives (Bug1). +- **安全加固后续(v3.8.15):** `auth_token` cookie 现在设置显式 30 天 `maxAge`,使会话按预期持续(Seg3);管理引导在 `INITIAL_PASSWORD` 保留在不安全的 `CHANGEME` 默认值时在启动时警告(Seg2);VS Code 路径令牌端点(`/api/v1/vscode/raw/[token]`)发出每次进程一次的安全警告,因为 API 密钥在 URL 中传输,可能通过日志/代理泄露(Seg4);系统版本路由通过 `npm root -g` 解析真实的全局安装路径,而非硬编码 `/app`(Bug3);自动更新模式检测分段匹配 `node_modules` 而非子字符串匹配,消除误报的"全局安装"(Bug1)。 -- **fix(cli):** rename the Node process title to `omniroute` so it shows correctly in ps/htop. (thanks @waguriagentic) +- **fix(cli):** 将 Node 进程标题重命名为 `omniroute`,使其在 ps/htop 中正确显示。(感谢 @waguriagentic) -- **dashboard (model picker):** guard against null model-alias values so opening Create Combo for a custom provider node no longer crashes. `ModelSelectModal`'s custom-provider branch filtered `modelAliases` entries with a raw `fullModel.startsWith(...)`, which threw a `TypeError` whenever an alias value was `null`/`undefined` (a stale/partial entry persisted to settings). The filter/map logic is extracted into a new `buildNodeAliasModels` helper (mirroring the sibling passthrough-alias guard, #485) that requires `typeof fullModel === "string"` before calling `.startsWith`. Regression guard: `tests/unit/model-select-null-alias-guard-2247.test.ts`. (thanks @wahyuzero) +- **dashboard(模型选择器):** 防范 null 模型别名值,使为自定义提供者节点打开创建组合不再崩溃。`ModelSelectModal` 的自定义提供者分支用原始 `fullModel.startsWith(...)` 过滤 `modelAliases` 条目,当别名值为 `null`/`undefined`(持久化到设置的陈旧/部分条目)时抛出 `TypeError`。过滤/映射逻辑提取到新的 `buildNodeAliasModels` 辅助函数(镜像同级透传别名守卫,#485),在调用 `.startsWith` 前要求 `typeof fullModel === "string"`。回归测试:`tests/unit/model-select-null-alias-guard-2247.test.ts`。(感谢 @wahyuzero) -- **fix(translator):** strip orphaned tool results (results with no matching tool call) across request formats to avoid upstream 400s. (thanks @warelik) +- **fix(translator):** 跨请求格式剥离孤立工具结果(没有匹配工具调用的结果),以避免上游 400 错误。(感谢 @warelik) -- **fix(kiro):** stop injecting a placeholder user turn on trailing tool-result turns so agentic loops aren't disrupted. (thanks @jetmiky) +- **fix(kiro):** 停止在尾随工具结果轮次上注入占位用户轮次,使代理循环不被中断。(感谢 @jetmiky) -- **fix(translator):** prevent doubled tool arguments in OpenAI-to-Claude responses (duplicate finish_reason guard + string tool-input passthrough). (thanks @vishalrajv) +- **fix(translator):** 防止 OpenAI→Claude 响应中重复的工具参数(重复 finish_reason 守卫 + 字符串工具输入透传)。(感谢 @vishalrajv) -- **codex (agent goal streams):** protect long-running agent goal streams so extended agent runs are no longer cut off prematurely. ([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — thanks @nguyenxvotanminh3) +- **codex(代理目标流):** 保护长时间运行的代理目标流,使扩展的代理运行不再被过早切断。([#5772](https://github.com/diegosouzapw/OmniRoute/pull/5772) — 感谢 @nguyenxvotanminh3) -- **sse (zero-width markers):** strip zero-width markers from streamed responses, matching the non-streaming path so streamed output is byte-clean parity. ([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — thanks @DKotsyuba) +- **sse(零宽标记):** 从流式响应中剥离零宽标记,与非流式路径匹配,使流式输出达到字节干净的对等性。([#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857) — 感谢 @DKotsyuba) -- **usage (om-usage endpoint):** restore the `om-usage` HTTP endpoint. ([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — thanks @Witroch4) +- **usage(om-usage 端点):** 恢复 `om-usage` HTTP 端点。([#5859](https://github.com/diegosouzapw/OmniRoute/pull/5859) — 感谢 @Witroch4) -- **sse (stream readiness):** tune adaptive stream-readiness timeouts so slow-first-token upstreams are handled more reliably. ([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — thanks @nguyenxvotanminh3) +- **sse(流就绪):** 调整自适应流就绪超时,使慢首令牌上游得到更可靠的处理。([#5767](https://github.com/diegosouzapw/OmniRoute/pull/5767) — 感谢 @nguyenxvotanminh3) -- **security (provider node URL):** harden provider node URL validation. ([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — thanks @nguyenxvotanminh3) +- **security(提供者节点 URL):** 加固提供者节点 URL 验证。([#5760](https://github.com/diegosouzapw/OmniRoute/pull/5760) — 感谢 @nguyenxvotanminh3) -- **cli (Windows doctor):** correct `rootDir` resolution in `doctor.mjs` on Windows. ([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — thanks @arssnndr) +- **cli(Windows 诊断):** 在 Windows 上纠正 `doctor.mjs` 中的 `rootDir` 解析。([#5845](https://github.com/diegosouzapw/OmniRoute/pull/5845) — 感谢 @arssnndr) -- **providers (Antigravity):** fix a 429 hang on credit exhaustion and apply a precise reset-time model lockout instead of stalling — cleaned re-implementation of #5823. ([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — thanks @Chewji9875 / @diegosouzapw) +- **providers(Antigravity):** 修复信用耗尽时的 429 挂起,并应用精确重置时间模型锁定而非停滞 — #5823 的清理重实现。([#5846](https://github.com/diegosouzapw/OmniRoute/pull/5846) — 感谢 @Chewji9875 / @diegosouzapw) -- **providers (qwen-web):** unblock the validator and chat completion — the retired endpoint is replaced and the missing SPA version header is now sent. ([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — thanks @janeza2) +- **providers(qwen-web):** 解除验证器和聊天补全的阻塞 — 退役端点已替换,缺失的 SPA 版本请求头现在被发送。([#5855](https://github.com/diegosouzapw/OmniRoute/pull/5855) — 感谢 @janeza2) -- **providers (kimi-web):** migrate to the `www.kimi.com` Connect-RPC API after `kimi.moonshot.cn` was retired. ([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — thanks @janeza2) +- **providers(kimi-web):** 在 `kimi.moonshot.cn` 退役后迁移到 `www.kimi.com` Connect-RPC API。([#5858](https://github.com/diegosouzapw/OmniRoute/pull/5858) — 感谢 @janeza2) -- **dashboard (CSRF):** unify the dashboard CSRF origin fallback so dynamic/public origins validate correctly. ([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — thanks @rdself) +- **dashboard(CSRF):** 统一仪表盘 CSRF 来源回退,使动态/公共来源正确验证。([#5856](https://github.com/diegosouzapw/OmniRoute/pull/5856) — 感谢 @rdself) -- **db (health check interval):** preserve `healthCheckInterval=0` across connection create/update instead of coercing it to a default. ([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — thanks @atomlong) +- **db(健康检查间隔):** 在连接创建/更新时保留 `healthCheckInterval=0`,而非将其强制转换为默认值。([#5822](https://github.com/diegosouzapw/OmniRoute/pull/5822) — 感谢 @atomlong) -- **sse (claude→codex streaming):** stop the reasoning-summary drop and duplicated deltas on claude→codex streaming — reasoning snapshots are now synthesized in TRANSLATE mode and the sequence-number watermark is tracked per-stream (#5786). ([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — thanks @diegosouzapw) +- **sse(claude→codex 流式):** 停止 claude→codex 流式上的推理摘要丢失和重复增量 — 推理快照现在在 TRANSLATE 模式下合成,序列号水印按流跟踪(#5786)。([#5832](https://github.com/diegosouzapw/OmniRoute/pull/5832) — 感谢 @diegosouzapw) -- **deps (runtime):** add the missing runtime dependencies `@toon-format/toon` and `safe-regex` so the published package resolves them at runtime. ([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — thanks @chirag127) +- **deps(运行时):** 添加缺失的运行时依赖 `@toon-format/toon` 和 `safe-regex`,使发布的包在运行时能够解析它们。([#5771](https://github.com/diegosouzapw/OmniRoute/pull/5771) — 感谢 @chirag127) -- **system (Windows auto-update):** route in-app auto-update `npm` calls through the win32 shell helper so updates run correctly on Windows (#5542). ([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — thanks @diegosouzapw) +- **system(Windows 自动更新):** 将应用内自动更新 `npm` 调用通过 win32 shell 辅助程序路由,使更新在 Windows 上正确运行(#5542)。([#5797](https://github.com/diegosouzapw/OmniRoute/pull/5797) — 感谢 @diegosouzapw) -- **dashboard (validation badge):** show a neutral badge for unsupported validation and make OAuth error messages clickable links (#5442, #5486). ([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — thanks @diegosouzapw) +- **dashboard(验证徽章):** 为不支持的验证显示中性徽章,使 OAuth 错误消息成为可点击链接(#5442、#5486)。([#5795](https://github.com/diegosouzapw/OmniRoute/pull/5795) — 感谢 @diegosouzapw) -- **providers (metadata):** correct stale/broken provider metadata (#5487, #5461, #5534, #5470). ([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — thanks @diegosouzapw) +- **providers(元数据):** 纠正过时/损坏的提供者元数据(#5487、#5461、#5534、#5470)。([#5790](https://github.com/diegosouzapw/OmniRoute/pull/5790) — 感谢 @diegosouzapw) -- **providers (local-catalog imports):** import intentional local-catalog-only providers instead of surfacing a 502 (#5460, #5465). ([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — thanks @diegosouzapw) +- **providers(本地目录导入):** 导入有意的仅本地目录提供者,而非显示 502(#5460、#5465)。([#5787](https://github.com/diegosouzapw/OmniRoute/pull/5787) — 感谢 @diegosouzapw) -- **proxyfetch (failover):** skip the failover retry for non-replayable request bodies so a consumed stream isn't re-sent empty. ([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — thanks @Ardem2025) +- **proxyfetch(故障转移):** 对不可重放的请求体跳过故障转移重试,使已消费的流不会空着重新发送。([#5770](https://github.com/diegosouzapw/OmniRoute/pull/5770) — 感谢 @Ardem2025) -- **batch (recovery):** persist batch item checkpoints during recovery so an interrupted batch resumes from where it left off. ([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — thanks @ag-linden) +- **batch(恢复):** 在恢复期间持久化批处理项检查点,使中断的批处理从离开的地方恢复。([#5753](https://github.com/diegosouzapw/OmniRoute/pull/5753) — 感谢 @ag-linden) -- **memory (Qdrant):** enabling Qdrant now activates it as the retrieval engine (the `auto` default never selected it) and adds inline guidance (#5597). ([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — thanks @diegosouzapw) +- **memory(Qdrant):** 启用 Qdrant 现在将其激活为检索引擎(`auto` 默认从未选择它)并添加内联指导(#5597)。([#5741](https://github.com/diegosouzapw/OmniRoute/pull/5741) — 感谢 @diegosouzapw) -- **chat (non-streaming aggregation):** harden non-streaming SSE aggregation against malformed upstream event sequences. ([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — thanks @rdself) +- **chat(非流式聚合):** 加固非流式 SSE 聚合,防范格式错误的上游事件序列。([#5746](https://github.com/diegosouzapw/OmniRoute/pull/5746) — 感谢 @rdself) -- **sse (cooldown parsing):** the anti-thundering-herd guard now tolerates numeric-epoch cooldown values. ([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — thanks @diegosouzapw) +- **sse(冷却解析):** 防雷鸣群守卫现在容忍数字 epoch 冷却值。([#5747](https://github.com/diegosouzapw/OmniRoute/pull/5747) — 感谢 @diegosouzapw) -- **api (body size):** raise the LLM API payload limit for the responses routes so larger requests aren't rejected. ([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — thanks @JxnLexn) +- **api(请求体大小):** 提高响应路由的 LLM API 有效载荷限制,使更大的请求不被拒绝。([#5652](https://github.com/diegosouzapw/OmniRoute/pull/5652) — 感谢 @JxnLexn) -- **providers (HuggingChat):** fix HuggingChat web-session routing (#5592). ([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — thanks @backryun) +- **providers(HuggingChat):** 修复 HuggingChat 网络会话路由(#5592)。([#5592](https://github.com/diegosouzapw/OmniRoute/pull/5592) — 感谢 @backryun) -- **sse (heap pressure):** bound the chat hot-path heap — pressure-aware admission, response cap, and clone reductions — to avoid OOM under load (#5152). ([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — thanks @josevictorferreira) +- **sse(堆压力):** 限制聊天热路径堆 — 压力感知准入、响应上限和克隆减少 — 以避免负载下的 OOM(#5152)。([#5425](https://github.com/diegosouzapw/OmniRoute/pull/5425) — 感谢 @josevictorferreira) -- **providers (M365 Copilot):** validate M365 Copilot web credentials. ([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — thanks @skyzea1) +- **providers(M365 Copilot):** 验证 M365 Copilot 网络凭据。([#5432](https://github.com/diegosouzapw/OmniRoute/pull/5432) — 感谢 @skyzea1) -- **providers (chatgpt-web):** restore the dot-form Pro model ids. ([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — thanks @Thinkscape) +- **providers(chatgpt-web):** 恢复点形式的 Pro 模型 ID。([#5549](https://github.com/diegosouzapw/OmniRoute/pull/5549) — 感谢 @Thinkscape) -- **security (error stacks):** avoid rendering error stacks in responses. ([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — thanks @KooshaPari) +- **security(错误堆栈):** 避免在响应中渲染错误堆栈。([#5624](https://github.com/diegosouzapw/OmniRoute/pull/5624) — 感谢 @KooshaPari) -- **security (linkify):** restrict `linkifyText` hrefs to an explicit `http(s)` scheme allowlist. ([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — thanks @diegosouzapw) +- **security(链接化):** 将 `linkifyText` href 限制为显式 `http(s)` 方案白名单。([#948d2d7](https://github.com/diegosouzapw/OmniRoute/commit/948d2d7f2) — 感谢 @diegosouzapw) -- **translator (doubled tool args):** prevent doubled tool-call arguments in the OpenAI→Claude translation path. ([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — thanks @diegosouzapw) +- **translator(重复工具参数):** 防止 OpenAI→Claude 翻译路径中重复的工具调用参数。([#5828](https://github.com/diegosouzapw/OmniRoute/pull/5828) — 感谢 @diegosouzapw) -- **translator (orphaned tool results):** strip orphaned tool-result turns across request formats so an upstream doesn't reject a tool result with no matching call. ([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — thanks @diegosouzapw) +- **translator(孤立工具结果):** 跨请求格式剥离孤立工具结果轮次,使上游不拒绝没有匹配调用的工具结果。([#5805](https://github.com/diegosouzapw/OmniRoute/pull/5805) — 感谢 @diegosouzapw) -- **translator (Gemini/Claude hardening):** re-apply lost defensive hardening for the Gemini merge path and Claude tool defaults. ([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — thanks @diegosouzapw) +- **translator(Gemini/Claude 加固):** 重新应用 Gemini 合并路径和 Claude 工具默认值的丢失防御性加固。([#5706](https://github.com/diegosouzapw/OmniRoute/pull/5706) — 感谢 @diegosouzapw) -- **kiro (tool-result turns):** stop injecting a placeholder user turn on tool-result turns, which corrupted otherwise-valid Kiro conversations. ([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — thanks @diegosouzapw) +- **kiro(工具结果轮次):** 停止在工具结果轮次上注入占位用户轮次,这破坏了原本有效的 Kiro 对话。([#5807](https://github.com/diegosouzapw/OmniRoute/pull/5807) — 感谢 @diegosouzapw) -- **providers (Kiro catalog):** add `claude-sonnet-5` to the Kiro model catalog. ([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — thanks @diegosouzapw) +- **providers(Kiro 目录):** 将 `claude-sonnet-5` 添加到 Kiro 模型目录。([#5796](https://github.com/diegosouzapw/OmniRoute/pull/5796) — 感谢 @diegosouzapw) -- **oauth (connection disambiguation):** disambiguate OAuth connections on username so two different identity providers no longer overwrite each other. ([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — thanks @diegosouzapw) +- **oauth(连接消歧):** 按用户名消除 OAuth 连接歧义,使两个不同的身份提供者不再相互覆盖。([#5803](https://github.com/diegosouzapw/OmniRoute/pull/5803) — 感谢 @diegosouzapw) -- **github (Copilot prefill):** drop the trailing assistant prefill for Copilot chat, which some Copilot models rejected. ([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — thanks @diegosouzapw) +- **github(Copilot 预填充):** 为 Copilot 聊天丢弃尾随 assistant 预填充,一些 Copilot 模型会拒绝它。([#5802](https://github.com/diegosouzapw/OmniRoute/pull/5802) — 感谢 @diegosouzapw) -- **mitm (hosts cleanup):** clean up privileged `/etc/hosts` entries on exit when possible so a crashed/interrupted run doesn't leave stale redirects behind. ([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — thanks @diegosouzapw) +- **mitm(hosts 清理):** 在可能时在退出时清理特权 `/etc/hosts` 条目,使崩溃/中断的运行不留下过时的重定向。([#5808](https://github.com/diegosouzapw/OmniRoute/pull/5808) — 感谢 @diegosouzapw) -- **dashboard (model picker):** guard null `modelAliases` values in the model picker so a connection with no aliases no longer throws. ([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — thanks @diegosouzapw) +- **dashboard(模型选择器):** 在模型选择器中防范 null `modelAliases` 值,使没有别名的连接不再抛出异常。([#5792](https://github.com/diegosouzapw/OmniRoute/pull/5792) — 感谢 @diegosouzapw) -- **dashboard (error boundaries):** add error boundaries for the Combos and MITM Proxy pages so a render error no longer blanks the whole dashboard. ([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — thanks @diegosouzapw) +- **dashboard(错误边界):** 为组合和 MITM 代理页面添加错误边界,使渲染错误不再使整个仪表盘空白。([#5788](https://github.com/diegosouzapw/OmniRoute/pull/5788) — 感谢 @diegosouzapw) -- **cli (process title):** rename the running process title to `omniroute`. ([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — thanks @diegosouzapw) +- **cli(进程标题):** 将运行进程标题重命名为 `omniroute`。([#5791](https://github.com/diegosouzapw/OmniRoute/pull/5791) — 感谢 @diegosouzapw) -- **compression (context-editing telemetry):** record Context Editing telemetry on the streaming path, not just the non-streaming path. ([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — thanks @diegosouzapw) +- **compression(上下文编辑遥测):** 在流式路径上记录上下文编辑遥测,而不仅是非流式路径。([#5761](https://github.com/diegosouzapw/OmniRoute/pull/5761) — 感谢 @diegosouzapw) -- **security (v3.8.15 hardening follow-ups):** land the Seg2/Seg3/Seg4/Bug3 hardening follow-ups from the v3.8.15 security review. ([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — thanks @diegosouzapw) +- **security(v3.8.15 加固后续):** 落地 v3.8.15 安全审查中的 Seg2/Seg3/Seg4/Bug3 加固后续。([#5512](https://github.com/diegosouzapw/OmniRoute/pull/5512) — 感谢 @diegosouzapw) -### 📝 Maintenance +### 📝 维护 -- **docs (architecture):** add `docs/architecture/ROUTER_BACKENDS.md` — an ADR pinning down how the routing engines (`ts` native, `bifrost`, `cliproxy`, `9router`, VibeProxy-compatible) relate to each other along two orthogonal axes (lifecycle: in-process / supervised / external vs. relay selection backend), answering the architecture questions raised in [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) (backend interface model, why CLIProxy spawns a process, feature-flag swapping, actionable route-contract errors). The typed router-backend registry the ADR describes lands separately via [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868). ([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891)) +- **docs(架构):** 新增 `docs/architecture/ROUTER_BACKENDS.md` — 一份 ADR,确定路由引擎(`ts` 原生、`bifrost`、`cliproxy`、`9router`、VibeProxy-compatible)如何沿两个正交轴相互关联(生命周期:进程内 / 受监督 / 外部 vs. 中继选择后端),回答 [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) 中提出的架构问题(后端接口模型、为什么 CLIProxy 生成进程、功能标志交换、可操作的路由契约错误)。ADR 描述的类型化路由器后端注册表将通过 [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868) 单独落地。([#5891](https://github.com/diegosouzapw/OmniRoute/pull/5891)) -- **tests (autoCombo):** stabilize the `getTaskFitnessWithSource identifies fitness_table as source for known models` unit test, which flaked whenever the models.dev capabilities DB was populated in CI: the fixture model `gpt-4o` is a real models.dev catalog id, so the fitness resolution chain returned `models_dev_tier` instead of the expected static `fitness_table` source. The fixture now uses `claude-sonnet` (a shortened alias absent from the models.dev catalog, matching the sibling resolution-chain test), which deterministically falls through to the static table — the exact `source` and score assertions are preserved (`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`). ([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — thanks @KooshaPari +- **tests(autoCombo):** 稳定化 `getTaskFitnessWithSource identifies fitness_table as source for known models` 单元测试,当 models.dev 能力数据库在 CI 中被填充时该测试会 flake:固定模型 `gpt-4o` 是真实的 models.dev 目录 ID,因此适应度解析链返回 `models_dev_tier` 而非预期的静态 `fitness_table` 源。固定装置现在使用 `claude-sonnet`(一个不在 models.dev 目录中的缩短别名,匹配同级解析链测试),确定性地回退到静态表 — 精确的 `source` 和分数断言被保留(`0.95` = `FITNESS_TABLE.coding["claude-sonnet"]`)。([#5890](https://github.com/diegosouzapw/OmniRoute/pull/5890)) — 感谢 @KooshaPari -- **oauth (dead-code removal):** delete the superseded legacy OAuth **service-class** hierarchy under `src/lib/oauth/services/`. The live OAuth flow runs through `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` (wired into the generic `oauth/[provider]/[action]` route); the old per-provider `class *Service extends OAuthService` implementations plus their barrel had **zero** production or test references. Removed `oauth.ts` (base class), `openai.ts`, `github.ts`, `claude.ts`, `codex.ts`, `antigravity.ts`, `qwen.ts`, `qoder.ts`, and the `index.ts` barrel (−1559 LOC). Kept the three still-live files that routes import **directly** by path: `kiro.ts` (Kiro import/exchange routes), `cursor.ts` (Cursor import route), and `codexImport.ts` (utility fns for the Codex bulk-import route). Proven safe by `typecheck:core` staying green (any live reference would fail the build) + a filesystem guard `tests/unit/oauth-legacy-services-removed.test.ts` pinning the removal against re-introduction. Salvage of the closed PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039). gaps v3.8.42 — T10 (5.7). +- **oauth(死代码移除):** 删除 `src/lib/oauth/services/` 下被取代的旧版 OAuth **服务类**层次结构。实时 OAuth 流通过 `src/lib/oauth/providers.ts` + `src/lib/oauth/providers/` 运行(接入通用 `oauth/[provider]/[action]` 路由);旧的每提供者 `class *Service extends OAuthService` 实现及其桶有**零**生产或测试引用。移除了 `oauth.ts`(基类)、`openai.ts`、`github.ts`、`claude.ts`、`codex.ts`、`antigravity.ts`、`qwen.ts`、`qoder.ts` 和 `index.ts` 桶(−1559 LOC)。保留了路由**按路径直接**导入的三个仍然活跃的文件:`kiro.ts`(Kiro 导入/交换路由)、`cursor.ts`(Cursor 导入路由)和 `codexImport.ts`(Codex 批量导入路由的实用函数)。由 `typecheck:core` 保持绿色证明安全(任何活跃引用都会使构建失败)+ 文件系统守卫 `tests/unit/oauth-legacy-services-removed.test.ts` 固定移除防止重新引入。关闭的 PR [#5039](https://github.com/diegosouzapw/OmniRoute/pull/5039) 的回收。v3.8.42 差距 — T10(5.7)。 -- **refactor (god-file decomposition):** extracted pure leaf modules across db, sse, usage, api, memory, evals, models, resilience, and dashboard god-files (types/mappers/helpers/pure-transform leaves; behavior-preserving, test-guarded): db/providers, db/proxies, db/models, db/settings, usageAnalytics, migrationRunner ([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714), [#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717), [#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705), [#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709), [#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722), [#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721)); sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag ([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824), [#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794), [#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736), [#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734)); usage families / callLogs / usageHistory / providerLimits ([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782), [#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725), [#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728), [#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730)); api provider-models discovery / unified-catalog ([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758), [#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699)); memory retrieval scoring ([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733)); evals golden-set suites ([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740)); modelsDevSync transform layer ([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743)); resilience settings split ([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745)); dashboard sidebarVisibility split ([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683)); executor shared-utility dedup + tests ([#5720](https://github.com/diegosouzapw/OmniRoute/pull/5720) — thanks @pizzav-xyz). — thanks @diegosouzapw +- **refactor(上帝文件分解):** 在 db、sse、usage、api、memory、evals、models、resilience 和 dashboard 上帝文件中提取纯叶子模块(类型/映射器/辅助函数/纯转换叶子;行为保留,测试守卫):db/providers、db/proxies、db/models、db/settings、usageAnalytics、migrationRunner([#5714](https://github.com/diegosouzapw/OmniRoute/pull/5714)、[#5717](https://github.com/diegosouzapw/OmniRoute/pull/5717)、[#5705](https://github.com/diegosouzapw/OmniRoute/pull/5705)、[#5709](https://github.com/diegosouzapw/OmniRoute/pull/5709)、[#5722](https://github.com/diegosouzapw/OmniRoute/pull/5722)、[#5721](https://github.com/diegosouzapw/OmniRoute/pull/5721));sse openai-to-gemini / cursor-protobuf / rate-limit-headers / reasoning-tag([#5824](https://github.com/diegosouzapw/OmniRoute/pull/5824)、[#5794](https://github.com/diegosouzapw/OmniRoute/pull/5794)、[#5736](https://github.com/diegosouzapw/OmniRoute/pull/5736)、[#5734](https://github.com/diegosouzapw/OmniRoute/pull/5734));usage families / callLogs / usageHistory / providerLimits([#5782](https://github.com/diegosouzapw/OmniRoute/pull/5782)、[#5725](https://github.com/diegosouzapw/OmniRoute/pull/5725)、[#5728](https://github.com/diegosouzapw/OmniRoute/pull/5728)、[#5730](https://github.com/diegosouzapw/OmniRoute/pull/5730));api provider-models discovery / unified-catalog([#5758](https://github.com/diegosouzapw/OmniRoute/pull/5758)、[#5699](https://github.com/diegosouzapw/OmniRoute/pull/5699));memory retrieval scoring([#5733](https://github.com/diegosouzapw/OmniRoute/pull/5733));evals golden-set suites([#5740](https://github.com/diegosouzapw/OmniRoute/pull/5740));modelsDevSync transform layer([#5743](https://github.com/diegosouzapw/OmniRoute/pull/5743));resilience settings split([#5745](https://github.com/diegosouzapw/OmniRoute/pull/5745));dashboard sidebarVisibility split([#5683](https://github.com/diegosouzapw/OmniRoute/pull/5683));executor shared-utility dedup + tests(... [truncated]) -- **chore (Bun script runner):** adopt Bun `1.3.10` as a locked, allow-listed **build/dev script runner** for a small set of validated TS gate/generator scripts (Node stays the published runtime): locked runtime dependency, CI script-checks + validated-scripts run under Bun, and a bun-safe pack validator. ([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615), [#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617), [#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612), [#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — thanks @KooshaPari; docs [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — thanks @diegosouzapw) +- **chore(Bun 脚本运行器):** 采用 Bun `1.3.10` 作为锁定、白名单的**构建/开发脚本运行器**,用于一小组经过验证的 TS 门禁/生成器脚本(Node 保持为发布的运行时):锁定的运行时依赖,CI 脚本检查和已验证脚本在 Bun 下运行,以及一个 bun 安全打包验证器。([#5615](https://github.com/diegosouzapw/OmniRoute/pull/5615)、[#5617](https://github.com/diegosouzapw/OmniRoute/pull/5617)、[#5612](https://github.com/diegosouzapw/OmniRoute/pull/5612)、[#5643](https://github.com/diegosouzapw/OmniRoute/pull/5643) — 感谢 @KooshaPari;文档 [#5703](https://github.com/diegosouzapw/OmniRoute/pull/5703) — 感谢 @diegosouzapw) -- **docs (sync & housekeeping):** i18n CHANGELOG mirror sync for the [3.8.43] section ([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789)); MCP tool count synced to 95 + routing-strategy count ([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732)); README faster/leaner install notes, refreshed metrics/badges, 17-strategy + Quota-Share listing, provider counts, and grammar fixes ([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713), [#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — thanks @chirag127); security docs for banned-keyword/account-ban detection ([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756)) and the full LOCAL_ONLY route set + GHSA advisory + audit path ([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748)); relay backend-routing contract clarification ([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — thanks @KooshaPari); release-freeze scoped to `/generate-release` only ([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839)); `.editorconfig` repository standards ([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — thanks @shiva24082). — thanks @diegosouzapw +- **docs(同步与整理):** i18n CHANGELOG 镜像同步 [3.8.43] 部分([#5789](https://github.com/diegosouzapw/OmniRoute/pull/5789));MCP 工具计数同步到 95 + 路由策略计数([#5732](https://github.com/diegosouzapw/OmniRoute/pull/5732));README 更快/更精简的安装说明,刷新指标/徽章,17 策略 + 配额共享列表,提供者计数和语法修复([#5713](https://github.com/diegosouzapw/OmniRoute/pull/5713)、[#5738](https://github.com/diegosouzapw/OmniRoute/pull/5738) — 感谢 @chirag127);安全文档:禁止关键词/账户封禁检测([#5756](https://github.com/diegosouzapw/OmniRoute/pull/5756))和完整 LOCAL_ONLY 路由集 + GHSA 公告 + 审计路径([#5748](https://github.com/diegosouzapw/OmniRoute/pull/5748));中继后端路由契约澄清([#5621](https://github.com/diegosouzapw/OmniRoute/pull/5621) — 感谢 @KooshaPari);发布冻结仅限于 `/generate-release`([#5839](https://github.com/diegosouzapw/OmniRoute/pull/5839));`.editorconfig` 仓库标准([#5879](https://github.com/diegosouzapw/OmniRoute/pull/5879) — 感谢 @shiva24082)。— 感谢 @diegosouzapw -- **test/ci (stabilization & ratchets):** guard the tsx/esm→esbuild boot transform ([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773)); align t3-web web-session metadata ([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835)); repoint the sidebar quota-share placement scan ([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711)); lightweight health probe for batch e2e ([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — thanks @KooshaPari); make release-green pre-flight gates visible + bounded ([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644)); stabilize nightly-mutation (tap.testFiles drift guard + anti-flake eps) ([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682)); close the QG v2 tail ([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681)); normalize check route paths on Windows ([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — thanks @KooshaPari); pass `sonar.projectVersion` to the SonarQube scan ([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880)); plus stryker `tap.testFiles` registration, compression-studio smoke re-anchoring, `rtk_discover` de-flake, and v3.8.43-cycle ratchet rebaselines (deadExports 225→227, complexity 1981→1982, cognitive-complexity 842→845, eslintWarnings 4121→4158→4199). — thanks @diegosouzapw +- **test/ci(稳定化与基准):** 守卫 tsx/esm→esbuild 启动转换([#5773](https://github.com/diegosouzapw/OmniRoute/pull/5773));对齐 t3-web 网络会话元数据([#5835](https://github.com/diegosouzapw/OmniRoute/pull/5835));重新定位侧边栏配额共享位置扫描([#5711](https://github.com/diegosouzapw/OmniRoute/pull/5711));批处理端到端轻量级健康探测([#5651](https://github.com/diegosouzapw/OmniRoute/pull/5651) — 感谢 @KooshaPari);使发布绿色预飞门禁可见且有界([#5644](https://github.com/diegosouzapw/OmniRoute/pull/5644));稳定化夜间变异(tap.testFiles 漂移守卫 + 防 flake eps)([#5682](https://github.com/diegosouzapw/OmniRoute/pull/5682));关闭 QG v2 尾部([#5681](https://github.com/diegosouzapw/OmniRoute/pull/5681));在 Windows 上标准化检查路由路径([#5613](https://github.com/diegosouzapw/OmniRoute/pull/5613) — 感谢 @KooshaPari);将 `sonar.projectVersion` 传递给 SonarQube 扫描([#5880](https://github.com/diegosouzapw/OmniRoute/pull/5880));加上 stryker `tap.testFiles` 注册、压缩工作室冒烟重新锚定、`rtk_discover` 防 flake 以及 v3.8.43 周期基准重新设定(deadExports 225→227、complexity 1981→1982、cognitive-complexity 842→845、eslintWarnings 4121→4158→4199)。— 感谢 @diegosouzapw -- **refactor (oauth):** remove dead legacy OAuth service classes. ([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — thanks @diegosouzapw) +- **refactor(oauth):** 移除死旧版 OAuth 服务类。([#5838](https://github.com/diegosouzapw/OmniRoute/pull/5838) — 感谢 @diegosouzapw) -### 🙌 Contributors +### 🙌 贡献者 -Thanks to everyone whose work landed in v3.8.43: +感谢所有在 v3.8.43 中贡献的各位: -| Contributor | PRs / Issues | -| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -| [@ag-linden](https://github.com/ag-linden) | #5753 | -| [@Ardem2025](https://github.com/Ardem2025) | #5770 | -| [@arssnndr](https://github.com/arssnndr) | #5845 | -| [@atomlong](https://github.com/atomlong) | #5822 | -| [@backryun](https://github.com/backryun) | #5592 | -| [@baslr](https://github.com/baslr) | direct commit / report | -| [@Chewji9875](https://github.com/Chewji9875) | #5563, #5579, #5846 | -| [@chirag127](https://github.com/chirag127) | #5738, #5771 | -| [@DKotsyuba](https://github.com/DKotsyuba) | #5857 | -| [@hartmark](https://github.com/hartmark) | #5834 | -| [@ishatiwari21](https://github.com/ishatiwari21) | #5799 | -| [@janeza2](https://github.com/janeza2) | #5855, #5858 | -| [@jetmiky](https://github.com/jetmiky) | direct commit / report | -| [@josevictorferreira](https://github.com/josevictorferreira) | #5425 | -| [@JxnLexn](https://github.com/JxnLexn) | #5652 | -| [@KooshaPari](https://github.com/KooshaPari) | #5613, #5621, #5624, #5629, #5643, #5651, #5890 | -| [@KunN-21](https://github.com/KunN-21) | direct commit / report | -| [@manhdzzz](https://github.com/manhdzzz) | direct commit / report | -| [@nguyenxvotanminh3](https://github.com/nguyenxvotanminh3) | #5760, #5767, #5772 | -| [@noir017](https://github.com/noir017) | direct commit / report | -| [@pizzav-xyz](https://github.com/pizzav-xyz) | #5720 | -| [@rdself](https://github.com/rdself) | #5746, #5856 | -| [@shiva24082](https://github.com/shiva24082) | #5879 | -| [@skyzea1](https://github.com/skyzea1) | #5432, #5701 | -| [@Stazyu](https://github.com/Stazyu) | #5557 | -| [@Thinkscape](https://github.com/Thinkscape) | #5549 | -| [@vishalrajv](https://github.com/vishalrajv) | direct commit / report | -| [@voravitl](https://github.com/voravitl) | direct commit / report | -| [@waguriagentic](https://github.com/waguriagentic) | direct commit / report | -| [@wahyuzero](https://github.com/wahyuzero) | direct commit / report | -| [@warelik](https://github.com/warelik) | direct commit / report | -| [@Witroch4](https://github.com/Witroch4) | #5731, #5859, #5863 | -| [@diegosouzapw](https://github.com/diegosouzapw) | maintainer — cycle reconciliation, release-close base-red fixes, god-file decomposition, compression/memory features | +| 贡献者 | PR / Issues | +| ------------------------------------------------------------ | -------------------------------------------------------------------- | +| [@ag-linden](https://github.com/ag-linden) | #5753 | +| [@Ardem2025](https://github.com/Ardem2025) | #5770 | +| [@arssnndr](https://github.com/arssnndr) | #5845 | +| [@atomlong](https://github.com/atomlong) | #5822 | +| [@backryun](https://github.com/backryun) | #5592 | +| [@baslr](https://github.com/baslr) | 直接提交 / 报告 | +| [@Chewji9875](https://github.com/Chewji9875) | #5563, #5579, #5846 | +| [@chirag127](https://github.com/chirag127) | #5738, #5771 | +| [@DKotsyuba](https://github.com/DKotsyuba) | #5857 | +| [@hartmark](https://github.com/hartmark) | #5834 | +| [@ishatiwari21](https://github.com/ishatiwari21) | #5799 | +| [@janeza2](https://github.com/janeza2) | #5855, #5858 | +| [@jetmiky](https://github.com/jetmiky) | 直接提交 / 报告 | +| [@josevictorferreira](https://github.com/josevictorferreira) | #5425 | +| [@JxnLexn](https://github.com/JxnLexn) | #5652 | +| [@KooshaPari](https://github.com/KooshaPari) | #5613, #5621, #5624, #5629, #5643, #5651, #5890 | +| [@KunN-21](https://github.com/KunN-21) | 直接提交 / 报告 | +| [@manhdzzz](https://github.com/manhdzzz) | 直接提交 / 报告 | +| [@nguyenxvotanminh3](https://github.com/nguyenxvotanminh3) | #5760, #5767, #5772 | +| [@noir017](https://github.com/noir017) | 直接提交 / 报告 | +| [@pizzav-xyz](https://github.com/pizzav-xyz) | #5720 | +| [@rdself](https://github.com/rdself) | #5746, #5856 | +| [@shiva24082](https://github.com/shiva24082) | #5879 | +| [@skyzea1](https://github.com/skyzea1) | #5432, #5701 | +| [@Stazyu](https://github.com/Stazyu) | #5557 | +| [@Thinkscape](https://github.com/Thinkscape) | #5549 | +| [@vishalrajv](https://github.com/vishalrajv) | 直接提交 / 报告 | +| [@voravitl](https://github.com/voravitl) | 直接提交 / 报告 | +| [@waguriagentic](https://github.com/waguriagentic) | 直接提交 / 报告 | +| [@wahyuzero](https://github.com/wahyuzero) | 直接提交 / 报告 | +| [@warelik](https://github.com/warelik) | 直接提交 / 报告 | +| [@Witroch4](https://github.com/Witroch4) | #5731, #5859, #5863 | +| [@diegosouzapw](https://github.com/diegosouzapw) | 维护者 — 周期协调、发布关闭基础红色修复、上帝文件分解、压缩/记忆功能 | --- ## [3.8.42] — 2026-06-30 -### ✨ New Features +### ✨ 新功能 -- **compression (pipeline):** add an honest default-on **inflation guard** to the stacked compression pipeline (T02 / Headroom H1). If the fully-stacked engines produce a body that did not actually shrink — its token count is `>=` the original — the compressed body is discarded and the verbatim original request is sent upstream instead, with a `pipeline-inflation-guard` warning recorded in the compression stats. This is safe by construction (the only fallback is the unmodified original, always a valid payload) and complements the existing opt-in per-step TV1 bail-out, which governs step-to-step advancement rather than the final output. New `open-sse/services/compression/pipelineGuards.ts`; wired at the single `finalizeStackedResult` choke point shared by the sync and async stacked paths. Regression guards (incl. an inflating-engine integration test) in `tests/unit/compression-pipeline-inflation-guard.test.ts`. +- **compression (pipeline):** 为级联压缩管线(T02 / Headroom H1)新增默认启用的**膨胀防护**。如果完全级联的引擎产生的输出实际上没有缩小——其 Token 数量 `>=` 原始值——则丢弃压缩结果,改为向上游发送原始请求,并在压缩统计中记录 `pipeline-inflation-guard` 警告。这通过构造保证了安全(唯一的回退是未修改的原始请求,始终是有效载荷),并补充了现有的可选每步 TV1 退出机制(后者控制步骤间的推进而非最终输出)。新增 `open-sse/services/compression/pipelineGuards.ts`;在同步和异步级联路径共享的单一 `finalizeStackedResult` 关键点接入。回归测试(含膨胀引擎集成测试)在 `tests/unit/compression-pipeline-inflation-guard.test.ts`。 -- **compression (caveman):** complete the German, French, and Japanese rule packs with the `dedup` (repeated-context collapsing) and `ultra` (abbreviation / terse) categories they were missing — these three languages previously shipped only `context`/`filler`/`structural`, while `en`/`es`/`id`/`pt-BR` had all five. So a de/fr/ja conversation compressed at higher intensities now collapses repeated boilerplate ("wie bereits besprochen" → "Siehe oben.", "comme mentionné précédemment" → "Voir ci-dessus.", "前述のとおり" → "(上記参照)") and abbreviates dense technical vocabulary (`Datenbank`→`DB`, `Authentifizierung`→`Auth`; `base de données`→`BD`, `authentification`→`auth`; `データベース`→`DB`, `アプリケーション`→`app`). Patterns mirror the existing `es` pack and stay ReDoS-safe (bounded literal alternations; the CJK pack uses no `\b` since Japanese has no word boundaries). Regression guard: `tests/unit/caveman-packs-de-fr-ja.test.ts` (packs load + validate + shrink a representative sample). gaps v3.8.42 — T05/C2. +- **compression (caveman):** 补齐德语、法语和日语规则包的 `dedup`(重复上下文折叠)和 `ultra`(缩写/精简)类别——这三种语言之前仅提供 `context`/`filler`/`structural`,而 `en`/`es`/`id`/`pt-BR` 拥有全部五种。因此以更高强度压缩的 de/fr/ja 对话现在会折叠重复的样板文本("wie bereits besprochen" → "Siehe oben."、"comme mentionné précédemment" → "Voir ci-dessus."、"前述のとおり" → "(上記参照)"),并缩写密集的技术词汇(`Datenbank`→`DB`、`Authentifizierung`→`Auth`;`base de données`→`BD`、`authentification`→`auth`;`データベース`→`DB`、`アプリケーション`→`app`)。模式与现有的 `es` 包一致,并保持 ReDoS 安全(有界字面量交替;CJK 包不使用 `\b`,因为日语没有词边界)。回归测试:`tests/unit/caveman-packs-de-fr-ja.test.ts`(包加载 + 验证 + 压缩代表性样本)。gaps v3.8.42 — T05/C2。 -- **compression (caveman):** add a **Chinese (zh / wenyan 文言) input-side rule pack** — the counterpart of the existing output-side `terse-cjk` style. New `rules/zh/{dedup,filler,ultra}.json` collapse repeated context ("如前所述" → "见上。"), drop pleasantries/hedging ("请帮我…/谢谢/我觉得"), strip sentence-final modal particles ("吗/呢/吧"), and abbreviate dense technical terms ("数据库"→"DB", "应用程序"→"app"). Chinese is now auto-detected: `detectCompressionLanguage` distinguishes zh from ja by Han-without-kana (kana is Japanese-exclusive, so a Han-heavy Japanese sentence still resolves to `ja`), and `zh` is listed in `listSupportedCompressionLanguages`. Patterns are ReDoS-safe (bounded literal alternations, no `\b` since CJK has no word boundaries). Regression guard: `tests/unit/caveman-packs-zh-wenyan.test.ts` (packs load + validate + shrink; zh/ja/non-CJK detection). gaps v3.8.42 — T05/C6. +- **compression (caveman):** 新增**中文(zh / wenyan 文言)输入侧规则包**——对应已有的输出侧 `terse-cjk` 风格。新增 `rules/zh/{dedup,filler,ultra}.json`,折叠重复上下文("如前所述" → "见上。")、删除客套/犹豫用语("请帮我…/谢谢/我觉得")、去除句末语气助词("吗/呢/吧"),并缩写密集技术术语("数据库"→"DB"、"应用程序"→"app")。中文现已支持自动检测:`detectCompressionLanguage` 通过"含汉字但不含假名"来区分 zh 和 ja(假名是日语专属,因此含大量汉字的日语语句仍会解析为 `ja`),`zh` 已列入 `listSupportedCompressionLanguages`。模式保持 ReDoS 安全(有界字面量交替,CJK 无语边界因此不使用 `\b`)。回归测试:`tests/unit/caveman-packs-zh-wenyan.test.ts`(包加载 + 验证 + 压缩;zh/ja/非 CJK 检测)。gaps v3.8.42 — T05/C6。 -- **compression (RTK):** add **Gradle** and **.NET CLI (`dotnet`)** to the RTK tool-output filter catalog. Tool output for `gradle`/`gradlew` and `dotnet build|test|restore|publish` is now recognized (both by command and by output content) and compressed: Gradle daemon/welcome banners and no-op `> Task … UP-TO-DATE/SKIPPED/FROM-CACHE` lines are dropped while `BUILD SUCCESSFUL/FAILED`, "What went wrong", and stack traces are preserved; the .NET build banner, copyright, and `Determining projects to restore`/`Restored …` chatter are dropped while `Build succeeded/FAILED`, `error CS####`/`warning CS####`, and test summaries are preserved. New builtin filters `engines/rtk/filters/{gradle,dotnet}.json` (with inline tests run by the catalog gate) plus `gradle`/`dotnet` entries in the command detector. Regression guard: `tests/unit/rtk-gradle-dotnet-filters.test.ts`. gaps v3.8.42 — T07/R9. +- **compression (RTK):** 将 **Gradle** 和 **.NET CLI(`dotnet`)** 添加到 RTK 工具输出过滤器目录。`gradle`/`gradlew` 和 `dotnet build|test|restore|publish` 的工具输出现在可被识别(通过命令和输出内容双重方式)并压缩:Gradle 守护进程/欢迎横幅和无效 `> Task … UP-TO-DATE/SKIPPED/FROM-CACHE` 行被丢弃,而 `BUILD SUCCESSFUL/FAILED`、"What went wrong" 和堆栈跟踪被保留;.NET 构建横幅、版权声明以及 `Determining projects to restore`/`Restored …` 等杂项被丢弃,而 `Build succeeded/FAILED`、`error CS####`/`warning CS####` 和测试摘要被保留。新增内置过滤器 `engines/rtk/filters/{gradle,dotnet}.json`(含由目录门控运行的内联测试),以及命令检测器中的 `gradle`/`dotnet` 条目。回归测试:`tests/unit/rtk-gradle-dotnet-filters.test.ts`。gaps v3.8.42 — T07/R9。 -### 🔧 Bug Fixes +### 🔧 问题修复 -- **providers (chatgpt-web):** fix `502 ChatGPT sentinel failed: Digest method not supported` on the **Electron desktop app**, which made every `chatgpt-web/*` request fail. The sentinel proof-of-work hashed with native `createHash("sha3-512")`, but Electron's Node is built against **BoringSSL, which does not implement the SHA-3 family** (electron/electron#30530), so the digest threw at construction — the provider was unusable on the desktop build (works under plain Node/OpenSSL). The PoW now hashes through a new runtime-portable helper (`open-sse/utils/sha3-512.ts`) that prefers the native digest and transparently falls back to a dependency-free pure-JS Keccak-f[1600] when native SHA-3 is absent. The fallback is validated bit-for-bit against native `createHash("sha3-512")` (300 random inputs) and the published FIPS-202 known-answer vectors. Regression guards in `tests/unit/chatgpt-web-sha3-boringssl-5531.test.ts`. ([#5531](https://github.com/diegosouzapw/OmniRoute/issues/5531)) +- **providers (chatgpt-web):** 修复在 **Electron 桌面应用**上出现的 `502 ChatGPT sentinel failed: Digest method not supported`,该错误导致所有 `chatgpt-web/*` 请求失败。哨兵工作量证明原本使用原生 `createHash("sha3-512")` 进行哈希,但 Electron 的 Node 是基于 **BoringSSL 构建的,而 BoringSSL 未实现 SHA-3 系列算法**(electron/electron#30530),因此摘要计算在构造阶段就抛出异常——该服务商在桌面版本中完全不可用(在纯 Node/OpenSSL 环境下正常工作)。PoW 现在通过一个新的运行时可移植辅助函数(`open-sse/utils/sha3-512.ts`)来计算哈希,优先使用原生摘要,当原生 SHA-3 不可用时透明回退到无依赖的纯 JS Keccak-f[1600] 实现。回退方案已针对原生 `createHash("sha3-512")`(300 个随机输入)和已发布的 FIPS-202 已知答案向量进行了逐位验证。回归测试:`tests/unit/chatgpt-web-sha3-boringssl-5531.test.ts`。([#5531](https://github.com/diegosouzapw/OmniRoute/issues/5531)) -- **providers (bytez):** fix Bytez key validation ("Provider validation endpoint not supported") and the chat base URL, verified live with a real key. Bytez **is** OpenAI-compatible at `…/models/v2/openai/v1`, but the registry stored the bare `…/models/v2` base, so the validation chat-probe hit `…/models/v2/chat/completions` → `404` → the misleading "endpoint not supported". Two parts: (1) the registry `baseUrl` now carries the full OpenAI-compat chat path (`…/models/v2/openai/v1/chat/completions`); (2) key validation no longer uses a chat probe — a Bytez account only serves models explicitly added to its catalog, so even valid keys 404 on any model id. A dedicated `validateBytezProvider` instead probes the **auth-only** `GET …/models/v2/list/tasks` endpoint (`200` ⇒ valid, `401/403` ⇒ invalid), which is independent of catalog provisioning. Regression guard: `tests/unit/bytez-validation-5422.test.ts`. ([#5422](https://github.com/diegosouzapw/OmniRoute/issues/5422)) +- **providers (bytez):** 修复 Bytez 密钥校验("Provider validation endpoint not supported")及聊天基础 URL,已使用真实密钥在线验证。Bytez **确实**是 OpenAI 兼容的(基础路径为 `…/models/v2/openai/v1`),但注册表中存储的是裸的 `…/models/v2` 路径,导致校验对话探测请求访问了 `…/models/v2/chat/completions` → `404` → 产生误导性的"不支持端点"错误。修复分两部分:(1) 注册表中的 `baseUrl` 现在携带完整的 OpenAI 兼容聊天路径(`…/models/v2/openai/v1/chat/completions`);(2) 密钥校验不再使用对话探测——Bytez 账户仅提供显式添加到其目录中的模型,因此即使有效的密钥对任何模型 ID 都会返回 404。改为通过专门的 `validateBytezProvider` 探测**仅用于认证的** `GET …/models/v2/list/tasks` 端点(`200` ⇒ 有效,`401/403` ⇒ 无效),该端点独立于目录配置。回归测试:`tests/unit/bytez-validation-5422.test.ts`。([#5422](https://github.com/diegosouzapw/OmniRoute/issues/5422)) -- **dashboard (provider add):** two provider-add UX fixes. (1) #5420 — the "Import Models" button now stays hidden for **tool-only providers** (web search / web fetch), not just `*-search` ids: `firecrawl` and `jina-reader` (declared `serviceKinds: ["webFetch"]`) previously showed an Import button that hit the `400 "does not support models listing"` route. A new capability check (`providerLacksModelListing` over the resolved serviceKinds) gates the section without ever hiding an LLM/media provider. (2) #5426 — Coze key validation no longer leaks the raw upstream envelope (`{code,msg,logId,from}`) into the UI; the Coze-shaped error becomes a friendly `Coze rejected the key: (code )` message (scoped to `provider === "coze"` so no other provider is affected). Regression guards: `tests/unit/model-listing-capability-5420.test.ts`, `tests/unit/coze-validation-error-5426.test.ts`. ([#5420](https://github.com/diegosouzapw/OmniRoute/issues/5420), [#5426](https://github.com/diegosouzapw/OmniRoute/issues/5426)) +- **dashboard (provider add):** 两项添加服务商 UI 修复。(1) #5420 — "导入模型"按钮现在也对**工具专用服务商**(网页搜索 / 网页抓取)保持隐藏,而不仅仅是 `*-search` ID:`firecrawl` 和 `jina-reader`(声明了 `serviceKinds: ["webFetch"]`)之前会显示导入按钮,点击后会触发 `400 "does not support models listing"` 路由。新增能力检查(`providerLacksModelListing`,基于已解析的 serviceKinds)来控制该区域,不会隐藏 LLM/媒体服务商。(2) #5426 — Coze 密钥校验不再向 UI 泄露原始上游响应包(`{code,msg,logId,from}`);Coze 格式的错误现在转换为友好的 `Coze rejected the key: (code )` 消息(限定在 `provider === "coze"` 范围内,不影响其他服务商)。回归测试:`tests/unit/model-listing-capability-5420.test.ts`、`tests/unit/coze-validation-error-5426.test.ts`。([#5420](https://github.com/diegosouzapw/OmniRoute/issues/5420), [#5426](https://github.com/diegosouzapw/OmniRoute/issues/5426)) -- **providers (friendliai, novita):** fix two provider registry endpoints that rejected valid keys (verified live with real keys). **FriendliAI** pointed at `…/dedicated/v1/chat/completions`, which `403 Forbidden`s a serverless `flp_*` token — switched to `…/serverless/v1/chat/completions` (+ a serverless `modelsUrl`). **Novita** pointed at the legacy `…/v3/…` base with a typo'd model id `ai-ai/llama-3.1-8b-instruct` (both `404`) — switched to the OpenAI-compatible `…/openai/v1/…` base + the valid `meta-llama/llama-3.1-8b-instruct` id. Regression guard: `tests/unit/provider-endpoints-friendliai-novita.test.ts`. ([#5430](https://github.com/diegosouzapw/OmniRoute/issues/5430), [#5455](https://github.com/diegosouzapw/OmniRoute/issues/5455)) +- **providers (friendliai, novita):** 修复两个拒绝有效密钥的服务商注册表端点(已使用真实密钥在线验证)。**FriendliAI** 指向 `…/dedicated/v1/chat/completions`,这会对无服务器 `flp_*` Token 返回 `403 Forbidden`——已切换到 `…/serverless/v1/chat/completions`(+ 无服务器 `modelsUrl`)。**Novita** 指向旧的 `…/v3/…` 基础路径,且模型 ID 存在拼写错误 `ai-ai/llama-3.1-8b-instruct`(两者均 `404`)——已切换到 OpenAI 兼容的 `…/openai/v1/…` 基础路径 + 正确的 `meta-llama/llama-3.1-8b-instruct` ID。回归测试:`tests/unit/provider-endpoints-friendliai-novita.test.ts`。([#5430](https://github.com/diegosouzapw/OmniRoute/issues/5430), [#5455](https://github.com/diegosouzapw/OmniRoute/issues/5455)) -- **providers (muse-spark):** align the Muse Spark Web (Meta AI) cookie copy with the live cookie name. The default session cookie migrated from the retired `abra_sess` to `ecto_1_sess` (`META_AI_DEFAULT_COOKIE`), but the provider form hint and one 401 auth-failure message still told users to paste `abra_sess` — a cookie that no longer exists. Both strings now name `ecto_1_sess`. Regression guard: `tests/unit/muse-spark-cookie-copy-5449.test.ts`. ([#5449](https://github.com/diegosouzapw/OmniRoute/issues/5449)) +- **providers (muse-spark):** 将 Muse Spark Web(Meta AI)Cookie 文案与实际 Cookie 名称对齐。默认会话 Cookie 已从已退役的 `abra_sess` 迁移到 `ecto_1_sess`(`META_AI_DEFAULT_COOKIE`),但服务商表单提示和一条 401 认证失败消息仍然提示用户粘贴 `abra_sess`——一个已不存在的 Cookie。两处字符串现在均使用 `ecto_1_sess`。回归测试:`tests/unit/muse-spark-cookie-copy-5449.test.ts`。([#5449](https://github.com/diegosouzapw/OmniRoute/issues/5449)) -- **dashboard (provider add):** fix three rough edges in the Add-API-Key / model-import flow reported across the provider-catalog audit. (1) The **Validation Model** and **Account ID** form fields shipped untranslated i18n stub copy (`"Validation Model Id Label"`, `"Account Id Placeholder"`, …) that surfaced verbatim in the modal — replaced with real labels/placeholders/hints in `en.json`. (2) Model import **silently fell back to the cached/local catalog**: the route already returned a `warning` ("API unavailable — using local catalog"), but `useModelImportHandlers` only read `models`/`error` and dropped it, so the user got local models with no indication — the warning is now surfaced as an import log line (new pure helper `extractImportWarning`). (3) The required connection-**name** field defaulted to `""`, which let browser autofill inject garbage (e.g. `wiw`) — it now defaults to `"main"`. Regression guard: `tests/unit/provider-add-ux-i18n-import-warning.test.ts`. ([#5421](https://github.com/diegosouzapw/OmniRoute/issues/5421), [#5428](https://github.com/diegosouzapw/OmniRoute/issues/5428), [#5429](https://github.com/diegosouzapw/OmniRoute/issues/5429), [#5431](https://github.com/diegosouzapw/OmniRoute/issues/5431), [#5435](https://github.com/diegosouzapw/OmniRoute/issues/5435)) +- **dashboard (provider add):** 修复在整个服务商目录审计中报告的三项"添加 API 密钥"/模型导入流程的粗糙之处。(1) **校验模型**和**账户 ID** 表单字段使用了未翻译的 i18n 占位文案(`"Validation Model Id Label"`、`"Account Id Placeholder"` 等),这些文案直接显示在模态框中——已替换为 `en.json` 中的真实标签/占位符/提示。(2) 模型导入**静默回退到缓存/本地目录**:路由已经返回了 `warning`("API unavailable — using local catalog"),但 `useModelImportHandlers` 只读取 `models`/`error` 而丢弃了它,因此用户获得本地模型却没有任何提示——警告现在以导入日志行的形式呈现(新增纯辅助函数 `extractImportWarning`)。(3) 必填的连接**名称**字段默认值为 `""`,导致浏览器自动填充可能注入垃圾数据(例如 `wiw`)——现在默认为 `"main"`。回归测试:`tests/unit/provider-add-ux-i18n-import-warning.test.ts`。([#5421](https://github.com/diegosouzapw/OmniRoute/issues/5421), [#5428](https://github.com/diegosouzapw/OmniRoute/issues/5428), [#5429](https://github.com/diegosouzapw/OmniRoute/issues/5429), [#5431](https://github.com/diegosouzapw/OmniRoute/issues/5431), [#5435](https://github.com/diegosouzapw/OmniRoute/issues/5435)) -- **services (installer):** fix `spawn EINVAL` when installing an embedded service (9Router / CLIProxy) on **Windows + Node.js 24+**. Node 24 stopped letting `child_process.execFile()` run `.cmd` batch files without a shell (nodejs/node#52554), and npm on Windows is `npm.cmd`, so `runNpm()` threw `EINVAL` the moment a user clicked **Install**. `runNpm` now enables `shell` on win32 only. To keep Hard Rule #13 intact under a shell — where the shell, not `execFile`, parses argv — the install `--prefix` (a `DATA_DIR` path that can legitimately contain spaces, e.g. `C:\Users\John Doe\.omniroute\…`) is now passed via the `npm_config_prefix` **environment variable** instead of an argv path, and the user-supplied install `version` is constrained to a dist-tag/semver shape (`SERVICE_VERSION_PATTERN`) at the route boundary so it can never carry shell metacharacters. With the prefix in the environment and the version validated, every remaining argv entry is a static flag. Regression guards: `tests/unit/services/installers/runNpm-shell-5379.test.ts` (+ existing `ninerouter.test.ts` aligned to npm's `npm_config_prefix` env). ([#5379](https://github.com/diegosouzapw/OmniRoute/issues/5379)) +- **services (installer):** 修复在 **Windows + Node.js 24+** 上安装内嵌服务(9Router / CLIProxy)时出现的 `spawn EINVAL`。Node 24 不再允许 `child_process.execFile()` 在没有 Shell 的情况下运行 `.cmd` 批处理文件(nodejs/node#52554),而 Windows 上的 npm 是 `npm.cmd`,因此用户点击**安装**时 `runNpm()` 立即抛出 `EINVAL`。`runNpm` 现在仅在 win32 上启用 `shell`。为了在第 13 号硬规则下安全启用 Shell(Shell 而非 `execFile` 来解析 argv),安装 `--prefix`(`DATA_DIR` 路径,可能包含空格,例如 `C:\Users\John Doe\.omniroute\…`)现在通过 `npm_config_prefix` **环境变量**传递,代替 argv 路径参数,并且用户提供的安装 `version` 在路由边界处受限于 dist-tag/semver 格式(`SERVICE_VERSION_PATTERN`),因此永远不会携带 Shell 元字符。prefix 在环境变量中、version 经过校验,其余每个 argv 条目都是静态标志。回归测试:`tests/unit/services/installers/runNpm-shell-5379.test.ts`(+ 现有的 `ninerouter.test.ts` 已对齐到 npm 的 `npm_config_prefix` 环境变量)。([#5379](https://github.com/diegosouzapw/OmniRoute/issues/5379)) -- **cli (serve):** restore `dist/tls-options.mjs` to the npm tarball — the opt-in native HTTPS/TLS sidecar (#5361) was copied into the staged `dist/` by the build but then **pruned** by the prepublish allowlist step, so `omniroute serve` crashed on the published 3.8.41 with `ERR_MODULE_NOT_FOUND` (`dist/server-ws.mjs` imports `./tls-options.mjs`). Added `tls-options.mjs` to `APP_STAGING_ALLOWED_EXACT_PATHS` (survives the prune) and `dist/tls-options.mjs` to `PACK_ARTIFACT_REQUIRED_PATHS` (the `check:pack-artifact` gate now fails loudly if it ever vanishes again — same guard pattern as `webdav-handler.mjs`). Regression guards in `tests/unit/pack-artifact-policy.test.ts`. ([#5452](https://github.com/diegosouzapw/OmniRoute/issues/5452) — thanks @KooshaPari for the parallel fix [#5494](https://github.com/diegosouzapw/OmniRoute/pull/5494)) +- **cli (serve):** 恢复 `dist/tls-options.mjs` 到 npm 包中——可选的原生 HTTPS/TLS 边车(#5361)在构建时被复制到暂存区 `dist/` 中,但随后在预发布白名单步骤中被**裁剪移除**,导致 `omniroute serve` 在已发布的 3.8.41 版本上因 `ERR_MODULE_NOT_FOUND` 崩溃(`dist/server-ws.mjs` 导入了 `./tls-options.mjs`)。已将 `tls-options.mjs` 添加到 `APP_STAGING_ALLOWED_EXACT_PATHS`(通过裁剪检查)和 `dist/tls-options.mjs` 添加到 `PACK_ARTIFACT_REQUIRED_PATHS`(`check:pack-artifact` 门控现在如果该文件再次消失会响亮失败——与 `webdav-handler.mjs` 使用相同的防护模式)。回归测试:`tests/unit/pack-artifact-policy.test.ts`。([#5452](https://github.com/diegosouzapw/OmniRoute/issues/5452) — 感谢 @KooshaPari 的并行修复 [#5494](https://github.com/diegosouzapw/OmniRoute/pull/5494)) -- **dashboard:** fix the **Add Provider / onboarding wizard** button silently doing nothing. The `/dashboard/providers/new` route was a redirect stub (it bounced straight back to `/dashboard/providers`), so every "Add Provider" button and dashboard widget link opened nothing, and the fully-built `ProviderOnboardingWizard` component stayed orphaned (never rendered by any route). The route now renders the wizard directly; auth is enforced centrally by the `(dashboard)` layout, same as the sibling provider routes. Regression guard in `tests/unit/onboarding-wizard-route-5427.test.ts`. ([#5427](https://github.com/diegosouzapw/OmniRoute/issues/5427)) +- **dashboard:** 修复**添加服务商/引导向导**按钮点击后无反应的 bug。`/dashboard/providers/new` 路由是一个重定向桩(直接跳回 `/dashboard/providers`),因此每个"添加服务商"按钮和仪表盘小部件链接都打开了空白页,完整构建的 `ProviderOnboardingWizard` 组件被孤立(从未被任何路由渲染)。该路由现在直接渲染向导;认证由 `(dashboard)` 布局集中执行,与其他同级服务商路由相同。回归测试:`tests/unit/onboarding-wizard-route-5427.test.ts`。([#5427](https://github.com/diegosouzapw/OmniRoute/issues/5427)) -- **db (import):** fix `EBUSY: resource busy or locked` when importing a database on **Windows**. The import route deleted the live `storage.sqlite` + WAL/`-shm`/`-journal` sidecars with a plain `fs.unlinkSync` immediately after `resetDbInstance()`, but Windows releases the SQLite file handle asynchronously after `close()` (mmap / antivirus), so the unlink raced and threw `EBUSY`. The route now deletes via `unlinkFileWithRetry` (EBUSY/EPERM backoff) — the same helper the restore path already uses. Regression guard in `tests/unit/db-import-ebusy-5406.test.ts`. ([#5406](https://github.com/diegosouzapw/OmniRoute/issues/5406), consolidated under [#5161](https://github.com/diegosouzapw/OmniRoute/issues/5161)) +- **db (import):** 修复在 **Windows** 上导入数据库时出现的 `EBUSY: resource busy or locked`。导入路由在 `resetDbInstance()` 之后立即使用普通 `fs.unlinkSync` 删除活动的 `storage.sqlite` + WAL/`-shm`/`-journal` 边车文件,但 Windows 在 `close()` 之后异步释放 SQLite 文件句柄(mmap / 杀毒软件),导致 unlink 竞态并抛出 `EBUSY`。路由现在通过 `unlinkFileWithRetry`(EBUSY/EPERM 退避)来删除——与恢复路径已经使用的辅助函数相同。回归测试:`tests/unit/db-import-ebusy-5406.test.ts`。([#5406](https://github.com/diegosouzapw/OmniRoute/issues/5406),合并至 [#5161](https://github.com/diegosouzapw/OmniRoute/issues/5161)) -- **build:** keep `ioredis` out of the client/CLI bundle — a dast-smoke regression revealed the module was being pulled into browser/Electron client-side chunks; adding it to the `SPAWN_CAPABLE_PREFIXES` leaf excludes it from client bundles while keeping it available on the server path. ([#5546](https://github.com/diegosouzapw/OmniRoute/pull/5546)) +- **build:** 将 `ioredis` 排除在客户端/CLI 打包之外——一项 dast-smoke 回归测试发现该模块被引入浏览器/Electron 客户端侧代码块;将其添加到 `SPAWN_CAPABLE_PREFIXES` 叶子节点可在服务端路径上保持可用,同时从客户端打包中排除。([#5546](https://github.com/diegosouzapw/OmniRoute/pull/5546)) -- **providers (mimocode):** route per-account traffic through SOCKS5 proxy dispatchers — each mimocode account's requests are now dispatched via its configured SOCKS5 proxy rather than the default direct connection. ([#5521](https://github.com/diegosouzapw/OmniRoute/pull/5521) — thanks @pizzav-xyz) +- **providers (mimocode):** 通过 SOCKS5 代理调度器路由每个账户的流量——每个 mimocode 账户的请求现在通过其配置的 SOCKS5 代理而非默认直连来发送。([#5521](https://github.com/diegosouzapw/OmniRoute/pull/5521) — 感谢 @pizzav-xyz) -- **providers:** persist the **Configured** provider filter selection across page reloads — the filter was resetting to "All" on every navigation. ([#5510](https://github.com/diegosouzapw/OmniRoute/pull/5510) — thanks @KooshaPari) +- **providers:** 在页面刷新时持久化**已配置**服务商过滤器选项——之前的过滤器在每次导航时都会重置为"All"。([#5510](https://github.com/diegosouzapw/OmniRoute/pull/5510) — 感谢 @KooshaPari) -- **providers (chatgpt-web):** support **GPT-5.5 Pro** model handoff — adds the model mapping and handoff routing needed for the GPT-5.5 Pro tier. ([#5536](https://github.com/diegosouzapw/OmniRoute/pull/5536) — thanks @Thinkscape) +- **providers (chatgpt-web):** 支持 **GPT-5.5 Pro** 模型交接——增加了 GPT-5.5 Pro 层级所需的模型映射和交接路由。([#5536](https://github.com/diegosouzapw/OmniRoute/pull/5536) — 感谢 @Thinkscape) -- **dashboard:** keep onboarding schemas browser-safe — the schema module imported a server-side `db` reference that crashed the browser bundle; it is now imported only on the server path. ([#5525](https://github.com/diegosouzapw/OmniRoute/pull/5525) — thanks @KooshaPari) +- **dashboard:** 保持引导 Schema 的浏览器安全性——Schema 模块导入了服务端的 `db` 引用,导致浏览器打包崩溃;现在仅在服务端路径上导入。([#5525](https://github.com/diegosouzapw/OmniRoute/pull/5525) — 感谢 @KooshaPari) -- **routing (bifrost):** add auto-fallback cooldown for bifrost targets — prevents rapid re-selection of a failing bifrost backend within the cooldown window, complementing the existing circuit-breaker mechanism. ([#5519](https://github.com/diegosouzapw/OmniRoute/pull/5519) — thanks @KooshaPari) +- **routing (bifrost):** 为 bifrost 目标添加自动容灾冷却期——在冷却窗口内防止快速重新选择失败的 bifrost 后端,与现有的熔断器机制互补。([#5519](https://github.com/diegosouzapw/OmniRoute/pull/5519) — 感谢 @KooshaPari) -- **providers (opencode-plugin):** bump the opencode plugin to **v0.2.0** and wire auto-publish on release so the plugin package tracks OmniRoute releases automatically. ([#5363](https://github.com/diegosouzapw/OmniRoute/pull/5363) — thanks @herjarsa) +- **providers (opencode-plugin):** 将 opencode 插件升级到 **v0.2.0** 并在发布时接入自动发布功能,使插件包自动跟踪 OmniRoute 发布节奏。([#5363](https://github.com/diegosouzapw/OmniRoute/pull/5363) — 感谢 @herjarsa) -- **rate-limit:** normalize queue refresh settings — aligns the queue-refresh interval configuration across rate-limit strategies so stale queues are released on a consistent schedule. ([#5499](https://github.com/diegosouzapw/OmniRoute/pull/5499) — thanks @KooshaPari) +- **rate-limit:** 规范化队列刷新设置——在各速率限制策略间对齐队列刷新间隔配置,使过期队列按一致的节奏释放。([#5499](https://github.com/diegosouzapw/OmniRoute/pull/5499) — 感谢 @KooshaPari) -- **fallback:** normalize provider error-rule header extraction — ensures fallback retry decisions correctly read all response headers regardless of casing, fixing cases where a provider's `Retry-After` or custom error header was silently dropped. ([#5473](https://github.com/diegosouzapw/OmniRoute/pull/5473) — thanks @KooshaPari) +- **fallback:** 规范化服务商错误规则头提取——确保容灾重试决策能正确读取所有响应头(不区分大小写),修复服务商的 `Retry-After` 或自定义错误头被静默丢弃的问题。([#5473](https://github.com/diegosouzapw/OmniRoute/pull/5473) — 感谢 @KooshaPari) -- **routing:** gate Claude adaptive-thinking defaults behind the feature flag — prevents the thinking budget from being injected into requests for models that do not support the extended-thinking parameter, avoiding upstream `400` errors on non-thinking Claude variants. ([#5480](https://github.com/diegosouzapw/OmniRoute/pull/5480) — thanks @KooshaPari) +- **routing:** 将 Claude 自适应思考默认值放在功能标志之后——防止思考预算被注入到不支持扩展思考参数的模型请求中,避免对非思考型 Claude 变体产生上游 `400` 错误。([#5480](https://github.com/diegosouzapw/OmniRoute/pull/5480) — 感谢 @KooshaPari) -- **ci:** fix post-merge CI regressions introduced by the dead-code sweep — restores test imports and type references broken when the ratchet landed before downstream consumers were updated. ([#5467](https://github.com/diegosouzapw/OmniRoute/pull/5467) — thanks @KooshaPari) +- **ci:** 修复由死代码清理引入的合并后 CI 回归——恢复了在 ratchet 先于下游消费者登陆时损坏的测试导入和类型引用。([#5467](https://github.com/diegosouzapw/OmniRoute/pull/5467) — 感谢 @KooshaPari) -- **sse:** treat terminal stream cancels as complete — an aborted SSE stream was being left in a partial state, causing downstream consumers to wait indefinitely for a final event that would never arrive. ([#5491](https://github.com/diegosouzapw/OmniRoute/pull/5491) — thanks @JxnLexn) +- **sse:** 将终端流取消视为完成——被中止的 SSE 流会被留在部分状态,导致下游消费者无限等待一个永远不会到达的最终事件。([#5491](https://github.com/diegosouzapw/OmniRoute/pull/5491) — 感谢 @JxnLexn) -- **api:** fix framing of non-streaming JSON responses — `stream: false` chat-completions responses were returned without correct content-length framing, causing some clients to misparse the response body. ([#5416](https://github.com/diegosouzapw/OmniRoute/pull/5416) — thanks @rdself) +- **api:** 修复非流式 JSON 响应的帧封装——`stream: false` 的聊天补全响应返回时缺少正确的 content-length 帧封装,导致某些客户端错误解析响应体。([#5416](https://github.com/diegosouzapw/OmniRoute/pull/5416) — 感谢 @rdself) -- **dashboard (tests):** protect dynamic dashboard endpoint tests with CSRF validation — the test suite was exercising dashboard API routes without CSRF tokens, masking a coverage gap for those endpoints. ([#5405](https://github.com/diegosouzapw/OmniRoute/pull/5405) — thanks @rdself) +- **dashboard (tests):** 使用 CSRF 校验保护动态仪表盘端点测试——测试套件在没有 CSRF Token 的情况下测试仪表盘 API 路由,掩盖了这些端点的覆盖率缺口。([#5405](https://github.com/diegosouzapw/OmniRoute/pull/5405) — 感谢 @rdself) -- **providers:** remove the dead **Phind** provider (service shut down) and deduplicate the **HuggingChat** catalog listing that had accumulated a stale duplicate entry. ([#5530](https://github.com/diegosouzapw/OmniRoute/pull/5530) — thanks @backryun) +- **providers:** 移除已关闭的 **Phind** 服务商(服务已停止运营),并清理 **HuggingChat** 目录列表中累积的过期重复条目。([#5530](https://github.com/diegosouzapw/OmniRoute/pull/5530) — 感谢 @backryun) -- **providers (longcat):** correct the LongCat free tier — **LongCat-2.0** is now GA; the one-time 10M-token promo (KYC required) is correctly reflected in the catalog, replacing the stale legacy beta entry. ([#5508](https://github.com/diegosouzapw/OmniRoute/pull/5508) — thanks @backryun) +- **providers (longcat):** 更正 LongCat 免费层——**LongCat-2.0** 现已正式发布;一次性 1000 万 Token 推广活动(需 KYC)已在目录中正确反映,替换了过时的旧版测试条目。([#5508](https://github.com/diegosouzapw/OmniRoute/pull/5508) — 感谢 @backryun) -### 📝 Maintenance +### 📝 维护 -- **dashboard (refactor):** consolidate the duplicate caveman on/off toggle from the compression settings tab onto the single-source panel (T11), eliminating the stale off-sync copy. ([#5524](https://github.com/diegosouzapw/OmniRoute/pull/5524)) +- **dashboard (refactor):** 将压缩设置标签页中重复的 caveman 开关整合到单一来源面板 (T11),消除过时的不同步副本。([#5524](https://github.com/diegosouzapw/OmniRoute/pull/5524)) -- **tests:** add quota guard for Claude-Code identity version lockstep (Phase 2) — asserts that the Claude-Code version reported in quota accounting stays in sync with the deployed version, preventing silent drift. ([#5514](https://github.com/diegosouzapw/OmniRoute/pull/5514)) +- **tests:** 添加 Claude-Code 身份版本同步的配额守护(第二阶段)——断言配额记账中报告的 Claude-Code 版本与已部署版本保持同步,防止静默漂移。([#5514](https://github.com/diegosouzapw/OmniRoute/pull/5514)) -- **docs:** add relay backend strategy guide documenting supported relay backend types, selection criteria, and configuration patterns. ([#5547](https://github.com/diegosouzapw/OmniRoute/pull/5547)) +- **docs:** 添加中继后端策略指南,文档化支持的中继后端类型、选择标准和配置模式。([#5547](https://github.com/diegosouzapw/OmniRoute/pull/5547)) -- **docs:** clarify bifrost relay backend environment variables — documents which env vars control bifrost's relay backend selection and failover behavior. ([#5520](https://github.com/diegosouzapw/OmniRoute/pull/5520) — thanks @KooshaPari) +- **docs:** 说明 bifrost 中继后端环境变量——文档化哪些环境变量控制 bifrost 的中继后端选择和容灾行为。([#5520](https://github.com/diegosouzapw/OmniRoute/pull/5520) — 感谢 @KooshaPari) -- **tests:** add relay routing fallback header behavior tests — regression guard asserting that fallback-triggered relay requests carry the correct forwarded headers through the routing layer. ([#5526](https://github.com/diegosouzapw/OmniRoute/pull/5526) — thanks @KooshaPari) +- **tests:** 添加中继路由容灾头行为测试——回归防护,断言容灾触发的中继请求通过路由层携带正确的转发头。([#5526](https://github.com/diegosouzapw/OmniRoute/pull/5526) — 感谢 @KooshaPari) -- **ci:** add npm `fetch-retry` configuration and codify the release-freeze protocol (Hard Rule #21) — reduces transient npm registry fetch failures in CI and establishes the documented procedure for freezing releases. ([#5506](https://github.com/diegosouzapw/OmniRoute/pull/5506)) +- **ci:** 添加 npm `fetch-retry` 配置并规范化发布冻结协议(第 21 号硬规则)——减少 CI 中瞬态的 npm 注册表拉取失败,并建立文档化的发布冻结流程。([#5506](https://github.com/diegosouzapw/OmniRoute/pull/5506)) -- **deps:** bump 11 production dependencies to their latest compatible versions. ([#5414](https://github.com/diegosouzapw/OmniRoute/pull/5414)) +- **deps:** 将 11 个生产依赖升级到最新兼容版本。([#5414](https://github.com/diegosouzapw/OmniRoute/pull/5414)) -- **deps:** bump Electron from 42.4.1 to 42.5.1 in `/electron`. ([#5413](https://github.com/diegosouzapw/OmniRoute/pull/5413)) +- **deps:** 将 `/electron` 中的 Electron 从 42.4.1 升级到 42.5.1。([#5413](https://github.com/diegosouzapw/OmniRoute/pull/5413)) -- **deps:** bump the development dependency group with 9 updates. ([#5415](https://github.com/diegosouzapw/OmniRoute/pull/5415)) +- **deps:** 将开发依赖组升级,共 9 项更新。([#5415](https://github.com/diegosouzapw/OmniRoute/pull/5415)) -- **maintenance (dead-code):** repo-wide sweep of unused exported symbols, types, and schemas — removes 35 no-longer-referenced exports across cloud-agent, a2a, SSE, memory, quota, skills, gamification, codex, qdrant, playground, provider catalog, and combo modules, reducing the exported API surface and eliminating stale misleading types. ([#5372](https://github.com/diegosouzapw/OmniRoute/pull/5372), [#5373](https://github.com/diegosouzapw/OmniRoute/pull/5373), [#5374](https://github.com/diegosouzapw/OmniRoute/pull/5374), [#5375](https://github.com/diegosouzapw/OmniRoute/pull/5375), [#5376](https://github.com/diegosouzapw/OmniRoute/pull/5376), [#5377](https://github.com/diegosouzapw/OmniRoute/pull/5377), [#5378](https://github.com/diegosouzapw/OmniRoute/pull/5378), [#5380](https://github.com/diegosouzapw/OmniRoute/pull/5380), [#5381](https://github.com/diegosouzapw/OmniRoute/pull/5381), [#5382](https://github.com/diegosouzapw/OmniRoute/pull/5382), [#5383](https://github.com/diegosouzapw/OmniRoute/pull/5383), [#5384](https://github.com/diegosouzapw/OmniRoute/pull/5384), [#5385](https://github.com/diegosouzapw/OmniRoute/pull/5385), [#5386](https://github.com/diegosouzapw/OmniRoute/pull/5386), [#5387](https://github.com/diegosouzapw/OmniRoute/pull/5387), [#5388](https://github.com/diegosouzapw/OmniRoute/pull/5388), [#5389](https://github.com/diegosouzapw/OmniRoute/pull/5389), [#5390](https://github.com/diegosouzapw/OmniRoute/pull/5390), [#5391](https://github.com/diegosouzapw/OmniRoute/pull/5391), [#5392](https://github.com/diegosouzapw/OmniRoute/pull/5392), [#5393](https://github.com/diegosouzapw/OmniRoute/pull/5393), [#5395](https://github.com/diegosouzapw/OmniRoute/pull/5395), [#5396](https://github.com/diegosouzapw/OmniRoute/pull/5396), [#5397](https://github.com/diegosouzapw/OmniRoute/pull/5397), [#5398](https://github.com/diegosouzapw/OmniRoute/pull/5398), [#5399](https://github.com/diegosouzapw/OmniRoute/pull/5399), [#5400](https://github.com/diegosouzapw/OmniRoute/pull/5400), [#5401](https://github.com/diegosouzapw/OmniRoute/pull/5401), [#5402](https://github.com/diegosouzapw/OmniRoute/pull/5402), [#5403](https://github.com/diegosouzapw/OmniRoute/pull/5403), [#5404](https://github.com/diegosouzapw/OmniRoute/pull/5404), [#5463](https://github.com/diegosouzapw/OmniRoute/pull/5463), [#5464](https://github.com/diegosouzapw/OmniRoute/pull/5464), [#5466](https://github.com/diegosouzapw/OmniRoute/pull/5466), [#5468](https://github.com/diegosouzapw/OmniRoute/pull/5468) — thanks @JxnLexn) +- **maintenance (dead-code):** 全仓库清理未使用的导出符号、类型和 Schema — 移除了 cloud-agent、a2a、SSE、memory、quota、skills、gamification、codex、qdrant、playground、服务商目录和 Combo 模块中 35 个不再被引用的导出,减少了导出 API 面并消除了过时误导性类型。([#5372](https://github.com/diegosouzapw/OmniRoute/pull/5372), [#5373](https://github.com/diegosouzapw/OmniRoute/pull/5373), [#5374](https://github.com/diegosouzapw/OmniRoute/pull/5374), [#5375](https://github.com/diegosouzapw/OmniRoute/pull/5375), [#5376](https://github.com/diegosouzapw/OmniRoute/pull/5376), [#5377](https://github.com/diegosouzapw/OmniRoute/pull/5377), [#5378](https://github.com/diegosouzapw/OmniRoute/pull/5378), [#5380](https://github.com/diegosouzapw/OmniRoute/pull/5380), [#5381](https://github.com/diegosouzapw/OmniRoute/pull/5381), [#5382](https://github.com/diegosouzapw/OmniRoute/pull/5382), [#5383](https://github.com/diegosouzapw/OmniRoute/pull/5383), [#5384](https://github.com/diegosouzapw/OmniRoute/pull/5384), [#5385](https://github.com/diegosouzapw/OmniRoute/pull/5385), [#5386](https://github.com/diegosouzapw/OmniRoute/pull/5386), [#5387](https://github.com/diegosouzapw/OmniRoute/pull/5387), [#5388](https://github.com/diegosouzapw/OmniRoute/pull/5388), [#5389](https://github.com/diegosouzapw/OmniRoute/pull/5389), [#5390](https://github.com/diegosouzapw/OmniRoute/pull/5390), [#5391](https://github.com/diegosouzapw/OmniRoute/pull/5391), [#5392](https://github.com/diegosouzapw/OmniRoute/pull/5392), [#5393](https://github.com/diegosouzapw/OmniRoute/pull/5393), [#5395](https://github.com/diegosouzapw/OmniRoute/pull/5395), [#5396](https://github.com/diegosouzapw/OmniRoute/pull/5396), [#5397](https://github.com/diegosouzapw/OmniRoute/pull/5397), [#5398](https://github.com/diegosouzapw/OmniRoute/pull/5398), [#5399](https://github.com/diegosouzapw/OmniRoute/pull/5399), [#5400](https://github.com/diegosouzapw/OmniRoute/pull/5400) — 感谢 @JxnLexn) -- **maintenance (DRY):** DRY consolidation of shared helpers — extracts 17 duplicated utilities into single shared modules: vscode metadata helpers, proxy route handlers, auth zip extractors, combo-builder model options, vscode tokenized-request helpers, quota strategy ranking helpers, recharts donut card, provider-specific validation, batch response formatter, Redis runtime helpers, version-manager request parsing, media-generation route helpers, service install helpers, settings transform schemas, relay stream finalizer, machine-id fallback, and node SQLite adapter. ([#5471](https://github.com/diegosouzapw/OmniRoute/pull/5471), [#5472](https://github.com/diegosouzapw/OmniRoute/pull/5472), [#5475](https://github.com/diegosouzapw/OmniRoute/pull/5475), [#5477](https://github.com/diegosouzapw/OmniRoute/pull/5477), [#5479](https://github.com/diegosouzapw/OmniRoute/pull/5479), [#5482](https://github.com/diegosouzapw/OmniRoute/pull/5482), [#5484](https://github.com/diegosouzapw/OmniRoute/pull/5484), [#5485](https://github.com/diegosouzapw/OmniRoute/pull/5485), [#5488](https://github.com/diegosouzapw/OmniRoute/pull/5488), [#5490](https://github.com/diegosouzapw/OmniRoute/pull/5490), [#5492](https://github.com/diegosouzapw/OmniRoute/pull/5492), [#5493](https://github.com/diegosouzapw/OmniRoute/pull/5493), [#5495](https://github.com/diegosouzapw/OmniRoute/pull/5495), [#5496](https://github.com/diegosouzapw/OmniRoute/pull/5496), [#5497](https://github.com/diegosouzapw/OmniRoute/pull/5497), [#5498](https://github.com/diegosouzapw/OmniRoute/pull/5498), [#5500](https://github.com/diegosouzapw/OmniRoute/pull/5500) — thanks @JxnLexn) +- **maintenance (DRY):** 共享辅助函数的 DRY 整合 — 将 17 个重复的工具函数提取到单一共享模块中:vscode 元数据辅助函数、代理路由处理器、auth zip 提取器、Combo 构建器模型选项、vscode Token 化请求辅助函数、配额策略排序辅助函数、recharts 环形图卡片、服务商特定校验、批次响应格式化器、Redis 运行时辅助函数、版本管理器请求解析、媒体生成路由辅助函数、服务安装辅助函数、设置转换 Schema、中继流终结器、machine-id 回退和 node SQLite 适配器。([#5471](https://github.com/diegosouzapw/OmniRoute/pull/5471), [#5472](https://github.com/diegosouzapw/OmniRoute/pull/5472), [#5475](https://github.com/diegosouzapw/OmniRoute/pull/5475), [#5477](https://github.com/diegosouzapw/OmniRoute/pull/5477), [#5479](https://github.com/diegosouzapw/OmniRoute/pull/5479), [#5482](https://github.com/diegosouzapw/OmniRoute/pull/5482), [#5484](https://github.com/diegosouzapw/OmniRoute/pull/5484), [#5485](https://github.com/diegosouzapw/OmniRoute/pull/5485), [#5488](https://github.com/diegosouzapw/OmniRoute/pull/5488), [#5490](https://github.com/diegosouzapw/OmniRoute/pull/5490), [#5492](https://github.com/diegosouzapw/OmniRoute/pull/5492), [#5493](https://github.com/diegosouzapw/OmniRoute/pull/5493), [#5495](https://github.com/diegosouzapw/OmniRoute/pull/5495), [#5496](https://github.com/diegosouzapw/OmniRoute/pull/5496), [#5497](https://github.com/diegosouzapw/OmniRoute/pull/5497), [#5498](https://github.com/diegosouzapw/OmniRoute/pull/5498), [#5500](https://github.com/diegosouzapw/OmniRoute/pull/5500) — 感谢 @JxnLexn) --- ## [3.8.41] — 2026-06-29 -### ✨ New Features +### ✨ 新功能 -- **feat(relay): selectable relay backend (TS / Bifrost / `auto`)** — the OpenAI-compatible relay endpoint can now route its hot path through a native Bifrost sidecar without clients changing URLs. `OMNIROUTE_RELAY_BACKEND` / `RELAY_ROUTING_BACKEND` = `ts | bifrost | auto`: defaults to the existing TypeScript relay; `auto` selects Bifrost when `BIFROST_BASE_URL` is set (and `BIFROST_ENABLED` ≠ `0`) and falls back to TS automatically if the sidecar is unreachable; `bifrost` keeps strict failure behavior. Auth, per-IP/token rate limits, prompt-injection checks, and model allowlists still run in the Next relay route before dispatch (control plane stays in the app); responses carry `X-Routing-Backend` / `X-Routing-Fallback`. Regression guards: `tests/unit/api/v1/relay-routing-backend.test.ts`, `tests/unit/api/v1/bifrost-sidecar.test.ts`. ([#5315](https://github.com/diegosouzapw/OmniRoute/pull/5315), #5316 — thanks @KooshaPari) +- **feat(relay): 可选择的中继后端(TS / Bifrost / `auto`)** — OpenAI 兼容的中继端点现在可以通过原生 Bifrost 边车路由其热路径,客户端无需更改 URL。`OMNIROUTE_RELAY_BACKEND` / `RELAY_ROUTING_BACKEND` = `ts | bifrost | auto`:默认使用现有的 TypeScript 中继;`auto` 在 `BIFROST_BASE_URL` 已设置(且 `BIFROST_ENABLED` ≠ `0`)时选择 Bifrost,并在边车不可达时自动回退到 TS;`bifrost` 保持严格的失败行为。认证、每 IP/Token 速率限制、提示注入检查和模型白名单仍在 Next 中继路由中于调度前运行(控制平面保留在应用内);响应携带 `X-Routing-Backend` / `X-Routing-Fallback`。回归测试:`tests/unit/api/v1/relay-routing-backend.test.ts`、`tests/unit/api/v1/bifrost-sidecar.test.ts`。([#5315](https://github.com/diegosouzapw/OmniRoute/pull/5315), #5316 — 感谢 @KooshaPari) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **translator (claude):** synthesize a minimal `user` turn when an OpenAI→Claude request carries **only** `system`/`developer` messages, so the request stops failing with `[400]: messages: at least one message is required`. `openaiToClaudeRequest` hoists every system/developer turn into Claude's top-level `system` field and filters them out of `messages`; an all-system input (OpenCode compaction / title-generation requests) left `messages: []`, which the Messages API rejects — surfacing in OpenCode as a mid-task `stream error` that drops the conversation. The guard fires only when `messages` would otherwise be empty (system instructions still drive the response), so non-empty requests are unaffected. ([#5342](https://github.com/diegosouzapw/OmniRoute/pull/5342) — thanks @wild-feather) -- **providers (gemini):** drop retired Google AI Studio model ids and align the catalog to what the live GenAI API actually serves (verified 2026-06-29 against the official deprecations page). Removes long-retired `gemini-1.5-pro`/`gemini-1.5-flash`, the shut-down `gemini-2.0-flash`/`gemini-2.0-flash-lite`, and dead experimentals; renames `gemini-3.1-flash-lite-preview` → the GA `gemini-3.1-flash-lite`; swaps the retired `text-embedding-004` for the live `gemini-embedding-001`/`gemini-embedding-2`; and adds graceful `modelDeprecation` forwards so legacy/renamed ids redirect to the GA model instead of 404ing. Native AI-Studio-direct image/video/music registration is intentionally out of scope (needs real executor work; those models stay reachable via Antigravity/Vertex/aggregators). ([#5337](https://github.com/diegosouzapw/OmniRoute/pull/5337) — thanks @backryun) -- **services (dashboard):** fix the embedded-services dashboard failures (#5298) — service supervisors are now lazily initialized from `/api/services/[name]/logs` so `cliproxy`/`9router` logs no longer 404 before bootstrap registers a supervisor; lifecycle buttons send JSON (empty install bodies default to `version: "latest"`, malformed JSON still returns `400 Invalid JSON body`); lifecycle and log-stream failures surface as actionable UI errors instead of silently showing no logs; Tailscale CGNAT `100.64.0.0/10` peers count as private-LAN local for local-only service access; a parent `/dashboard/context` → `/dashboard/context/settings` redirect stops RSC prefetch 404s; and `/api/v1/providers/{cliproxyapi,9router}/models` return synced embedded-service models instead of `invalid_provider`. ([#5299](https://github.com/diegosouzapw/OmniRoute/pull/5299), #5298 — thanks @KooshaPari) -- **thinking (claude):** fix three independent defects in Claude adaptive-thinking on the OpenAI-compatible path (Cursor → Claude OAuth). **(A)** the dashboard Thinking-Budget setting was dropped on every restart — `setThinkingBudgetConfig` was never called at boot, so a saved `{mode:"adaptive"…}` silently reverted to passthrough; it's now hydrated from settings in `server-init`. **(B)** the Claude executor force-injected adaptive thinking _after_ translation, ignoring the operator's budget — it now honors `mode:"auto"` (strip) while keeping the default (passthrough) behavior byte-identical so native Claude Code is unaffected, and remaps an operator `thinking.type:"enabled"` to the `adaptive` shape Opus 4.7/4.8 require (`enabled` → 400). **(D)** on replay, signature-less `reasoning_content` was reconstructed as a `thinking` block carrying a fabricated signature → Anthropic `400 "Invalid signature in thinking block"`; it now emits a signature-less `redacted_thinking` block (real signatures are still preserved verbatim). Regression guards: `tests/unit/thinking-budget-hydration-5312.test.ts`, `base-thinking-budget-config-5312.test.ts`, `openai-to-claude-redacted-replay-5312.test.ts` (existing #5123/#4479/#2454 suites stay green). The `` content-marker channel mismatch (RC-C, shared with #5245) is tracked as a follow-up pending a live Anthropic validation. ([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312) — thanks @vitalNohj) -- **opencode (proxy pool):** the OpenCode Free per-account proxy modal now offers the global **Proxy Pool dropdown** (by-id reference) instead of forcing manual Host/Port/credentials on every account — Gap 1 of #5217. A **Saved / Custom** toggle: "Saved" picks a pre-saved proxy from `GET /api/settings/proxies` and stores `{fingerprint, proxyId}`, so updating that pool proxy applies to every account using it; "Custom" keeps the manual inputs (stored inline) as an escape hatch. Resolution happens server-side (`resolveAccountProxiesFromRegistry`) so the executor still receives a resolved proxy unchanged; existing inline entries keep working and an unknown/deleted `proxyId` degrades safely to direct. Regression guards: `tests/unit/noauth-proxy-resolution.test.ts`, `tests/unit/ui/noauth-account-card.test.tsx`. ([#5217](https://github.com/diegosouzapw/OmniRoute/issues/5217) Gap 1 — thanks @daniij) -- **thinking (claude):** let reasoning_content-native clients (e.g. Cursor) opt out of the `` close-marker so it no longer leaks an orphan `` into visible `content` (RC-C of #5312, shared with #5245). The marker-suppression machinery already existed (UA allowlist, #5348) but Cursor's UA was deliberately excluded; this adds an explicit request header `x-omniroute-thinking-marker: off` (also `on`/`keep` to force-keep) that overrides the UA policy. With the header absent the behavior is byte-identical — Claude Code/Cursor-composer clients that scan `content` for the marker (#4633) still receive it. Regression guard: `tests/unit/think-close-marker-suppress-5245.test.ts` (#5123 case-b + #4479 stay green). ([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312), [#5245](https://github.com/diegosouzapw/OmniRoute/issues/5245) — thanks @vitalNohj, @wild-feather) -- **cors:** browser/Electron clients (e.g. Wayland AI) can now use OmniRoute as an OpenAI-compatible provider out-of-the-box. The token-authenticated API surface (`/v1/*`, `/v1beta/*`) now returns a permissive `Access-Control-Allow-Origin` (echoes the request `Origin`, `*` when absent) by default — matching 9router and the OpenAI-compatible ecosystem — so a renderer `fetch` can read the response instead of failing CORS-blocked as "site not found" / empty catalog (while `curl`, which sends no preflight, worked). This is **safe**: those routes auth via `Authorization`/`x-api-key` headers browsers never auto-attach (no credentialed-session/CSRF exposure), and `Access-Control-Allow-Credentials` is never paired with the echo/wildcard. Cookie-authed **MANAGEMENT/dashboard routes stay exactly fail-closed**; `CORS_ALLOW_ALL`/`CORS_ALLOWED_ORIGINS` still take precedence. Regression guards: `tests/unit/cors/origins.test.ts`, `tests/unit/authz/pipeline.test.ts`. (Bug 2 of [#5242](https://github.com/diegosouzapw/OmniRoute/issues/5242) — thanks @jonlwheat2-gif) -- **grok-web:** forward the Cloudflare clearance cookies and stop mislabeling IP-reputation blocks as a bad cookie. "Check cookie" returned `Invalid SSO cookie` even with a valid, complete browser session — but the cookie parser was never the problem (it robustly extracts `sso`/`sso-rw` from a full DevTools header). Two real gaps fixed: **(1)** `buildGrokCookieHeader` now forwards `cf_clearance` and `__cf_bm` when pasted (it dropped them before; AIClient2API forwards them too) — strictly additive, a bare `sso` blob still yields exactly `sso=…`; **(2)** when the user supplied a `cf_clearance`, a 401 / invalid-credentials-403 from grok.com is now surfaced as an IP-reputation/anti-bot block (cf_clearance is IP+TLS+UA-pinned and can't be replayed from a different machine) instead of the misleading "Invalid SSO cookie — re-paste". A bare cookie with no clearance still gets the re-paste hint. Regression guards in `web-cookie-auth.test.ts` + `provider-validation-specialty.test.ts`. ([#5350](https://github.com/diegosouzapw/OmniRoute/issues/5350) — thanks @SeaXen) -- **cli (serve):** opt-in native **HTTPS/TLS** for `omniroute serve` — so strict-CSP Electron apps and browsers can reach OmniRoute over `https://` instead of plain `http://localhost`. Provide `--tls-cert --tls-key ` (or `OMNIROUTE_TLS_CERT`/`OMNIROUTE_TLS_KEY`) and the standalone server terminates TLS on the same listener (no extra port/proxy); WebSocket upgrade (live dashboard + `/v1` streaming) works over `wss://` unchanged since `https.Server extends http.Server`. With no TLS flags the HTTP path is byte-identical to before; only one of cert/key, or an unreadable path, logs a warning and stays HTTP (never half-enables, never crashes). Auto-generated self-signed certs for localhost are a follow-up; for now provide an explicit cert/key (or front OmniRoute with a TLS terminator). Regression guard: `tests/unit/tls-options.test.ts`. (Bug 1C of [#5242](https://github.com/diegosouzapw/OmniRoute/issues/5242) — thanks @jonlwheat2-gif) -- **opencode/observability:** make OpenCode Free account/proxy rotation visible and fix two real defects surfaced alongside it. **(1)** the per-request rotation selection log (`dispatch via account … through proxy …`) was `debug` (hidden at default `APP_LOG_LEVEL=info`) — promoted to `info` so the shuffle/cooldown lifecycle is auditable (token stays masked). **(2)** `[ProxyEgress]` reported `proxy=direct` even when an account proxy was applied, because the egress logger ran outside the executor's nested proxy context — the effective applied proxy is now captured (via an applied-proxy sink threaded through the proxy AsyncLocalStorage) and reflected in the egress log. **(3)** `[callLogs] too many SQL variables` — `deleteCallLogRowsByIds` deleted up to 5000 ids in one `IN (…)`, exceeding SQLite's ~999 bound-param cap and aborting log trimming/retention; ids are now chunked (≤500 per statement). Regression guards: `tests/unit/call-log-trim-sql-vars-5217.test.ts`, `apply-executor-proxy-info-5217.test.ts`, extended `opencode-proxy-rotation-4954.test.ts`. The Proxy Pool dropdown (by-id) UI (Gap 1) is a follow-up requiring browser validation. ([#5217](https://github.com/diegosouzapw/OmniRoute/issues/5217) — thanks @daniij) -- **chatgpt-web:** wire tool/function calling into the `chatgpt-web` provider. It was the only web-session executor that never read `body.tools` — both response builders hardcoded `finish_reason:"stop"` and emitted only content, so tool calls were silently dropped (the model answered in prose). It now uses the shared `webTools` prompt-emulation shim (a ``-contract system message + `{…}` response parsing) exactly like its 9 sibling executors (qwen-web, perplexity-web, …) — it was simply omitted from the #3259 rollout. Tool mode buffers and emits `tool_calls` + `finish_reason:"tool_calls"` (gated off the image-gen path); plain chat is unchanged. Regression guard: `tests/unit/chatgpt-web-tools-5240.test.ts`. ([#5240](https://github.com/diegosouzapw/OmniRoute/issues/5240) — thanks @Rougler) -- **oauth/dashboard:** fix the persistent/false Antigravity "Token Expired" badge (continuation of #3679/#3850). Two causes: **(1)** new OAuth connections never set `tokenExpiresAt` (only `expiresAt`), so the dashboard badge — which prefers `tokenExpiresAt || expiresAt` — fell back to the original grant clock and could flash a false "Token Expired" until the first background refresh. Creation now mirrors `expiresAt` into `tokenExpiresAt` across all 5 OAuth create paths (a shared `buildOAuthConnectionCreatePayload`), consistent with every refresh path which already writes both. **(2)** when a refresh-capable connection has no usable refresh token, the health-check sweep silently skipped it, leaving `testStatus="active"` forever while the cosmetic badge showed expired; it now surfaces a terminal `testStatus="expired"` ("needs re-auth"), tightly gated so it never clobbers non-refresh providers or already-terminal/cooldown states. Regression guards: `tests/unit/oauth-connection-tokenexpiresat-5326.test.ts`, `tests/unit/token-health-no-refresh-token-expired-5326.test.ts`. ([#5326](https://github.com/diegosouzapw/OmniRoute/issues/5326)) -- **routing:** auto-disable a depleted API key on upstream `402 "Insufficient account balance"` for API Key Round-Robin connections (multiple keys in one connection's `extraApiKeys`). The per-connection path already terminalized 402 (→ `credits_exhausted`), but the per-KEY health tracker (`recordKeyHealthStatus`) only recorded failures for `401`, so a 402-depleted key stayed in rotation and kept getting retried. Now a 402 marks the current key invalid immediately (terminal — balance won't recover mid-session) via a new `recordKeyTerminal`, so the rotator skips it and falls over to the next healthy key; the state persists across restarts. Also added `insufficient balance`/`insufficient_balance`/`insufficient account balance` to the credits-exhausted body signals so non-402 out-of-credit responses terminalize too. Regression guard: `tests/unit/key-health-402-disable-5239.test.ts`. ([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) — thanks @muflifadla38) -- **cli:** `omniroute serve` no longer discards a user-set `NODE_OPTIONS=--max-old-space-size=…`. It used to unconditionally overwrite `NODE_OPTIONS` (and pass an explicit `--max-old-space-size` CLI arg) with the calibrated default, so a user who exported `--max-old-space-size=8192` still ran at the old cap and OOM'd (#5238 reporter set 8192, crashed at ~505MB). Now it mirrors the Electron and standalone launchers: if `NODE_OPTIONS` already pins the heap, that value wins (and the duplicate CLI arg is suppressed); otherwise the calibrated `--max-old-space-size` is appended, preserving unrelated flags. Regression guard: `tests/unit/serve-node-options-preserve-5238.test.ts`. (Defect C of [#5238](https://github.com/diegosouzapw/OmniRoute/issues/5238); the `b.mask`/OOM-root parts are tracked separately.) -- **dashboard:** restore the `{active}/{total} active` model-count badge in a provider's **Available Models** toolbar (provider detail page). It was dropped during the v3.8.13 god-file decomposition (#3327) — the `ModelVisibilityToolbar` still received `activeCount`/`totalCount` but they were orphaned as unused `_`-prefixed params and the rendering `` was never carried over (the `modelsActiveCount` i18n key stayed). Re-wired the existing props to the existing key; zero data-layer or i18n change. Regression guard: `modelVisibilityToolbarActiveCount.test.tsx`. ([#5264](https://github.com/diegosouzapw/OmniRoute/issues/5264)) -- **rerank:** `/v1/rerank` no longer rejects SiliconFlow and DeepInfra Qwen3-Reranker models with `400 "Invalid rerank model"` even though `/v1/models` lists them. The model-ID parser was never the problem (it already splits on the first slash, so `siliconflow/Qwen/Qwen3-Reranker-8B` parses correctly) — `siliconflow` and `deepinfra` were just missing from the rerank provider registry. Added both: SiliconFlow as Cohere-compatible, DeepInfra via a new `deepinfra` adapter (model in the URL path `POST /v1/inference/`, `{queries,documents}` request, positional `{scores}` response mapped to Cohere `results[]`). Regression guard: `tests/unit/rerank-providers-5332.test.ts`. ([#5332](https://github.com/diegosouzapw/OmniRoute/issues/5332) — thanks @maikokan) -- **authz/dashboard:** stop rejecting every dashboard mutation with `403 INVALID_ORIGIN` when the dashboard is reached over a LAN IP / non-localhost host. The origin-pinning check (#5278) only accepted the configured `*_PUBLIC_BASE_URL` (typically `http://localhost:20128`) plus the internal `request.url` origin — which Next.js standalone reports as the bind host, not the real `Host`. So opening the dashboard at e.g. `http://192.168.0.15:20128` made the browser's same-origin `Origin` match no candidate, and **every** POST/PUT/DELETE (save API key, save provider, test connection) failed while GETs still worked. Two fixes: **(a)** the request `Host` (or a trusted `X-Forwarded-Host`) is now accepted as a valid mutation origin, gated by two independent checks — the token-stamped socket peer must be loopback/private-LAN **and** the Host itself must be a loopback/private-LAN IP literal, so a DNS-rebinding domain (which classifies as `remote`) can never become a trusted origin and the protocol is pinned to the actual connection; **(b)** the `INVALID_ORIGIN` response now carries an actionable message (set `OMNIROUTE_PUBLIC_BASE_URL`) and the dashboard surfaces API error `.message` via a shared `extractApiErrorMessage` helper instead of rendering the raw error object. Regression guards: `tests/unit/authz/public-origin.test.ts` (direct LAN/loopback + DNS-rebinding defense), `tests/unit/api-error-message-5340.test.ts`. ([#5340](https://github.com/diegosouzapw/OmniRoute/issues/5340)) +- **translator (claude):** 当 OpenAI→Claude 请求**仅**包含 `system`/`developer` 消息时,合成一个最小化的 `user` 轮次,使请求不再因 `[400]: messages: at least one message is required` 而失败。`openaiToClaudeRequest` 将所有 system/developer 轮次提升到 Claude 的顶层 `system` 字段并从 `messages` 中过滤掉;纯 system 输入(OpenCode 压缩/标题生成请求)会导致 `messages: []`,Messages API 会拒绝——在 OpenCode 中表现为丢失对话的任务中途 `stream error`。该防护仅在 `messages` 原本为空时触发(system 指令仍然驱动响应),因此非空请求不受影响。([#5342](https://github.com/diegosouzapw/OmniRoute/pull/5342) — 感谢 @wild-feather) +- **providers (gemini):** 移除已退役的 Google AI Studio 模型 ID,并使目录与实际运行的 GenAI API 对齐(2026-06-29 根据官方弃用页面验证)。移除长期退役的 `gemini-1.5-pro`/`gemini-1.5-flash`、已关闭的 `gemini-2.0-flash`/`gemini-2.0-flash-lite` 和无效实验版;将 `gemini-3.1-flash-lite-preview` 重命名为 GA 版 `gemini-3.1-flash-lite`;将退役的 `text-embedding-004` 替换为实际运行中的 `gemini-embedding-001`/`gemini-embedding-2`;并添加优雅的 `modelDeprecation` 转发,使旧版/重命名的 ID 重定向到 GA 模型而非 404。原生 AI-Studio 直连的图像/视频/音乐注册有意不在本次范围内(需要实际的执行器工作;这些模型仍可通过 Antigravity/Vertex/聚合器访问)。([#5337](https://github.com/diegosouzapw/OmniRoute/pull/5337) — 感谢 @backryun) +- **services (dashboard):** 修复内嵌服务仪表盘故障 (#5298) — 服务监控器现在从 `/api/services/[name]/logs` 延迟初始化,因此 `cliproxy`/`9router` 日志在引导注册监控器之前不再 404;生命周期按钮发送 JSON(空安装体默认 `version: "latest"`,格式错误的 JSON 仍返回 `400 Invalid JSON body`);生命周期和日志流故障现在以可操作的 UI 错误呈现,而非静默显示无日志;Tailscale CGNAT `100.64.0.0/10` 对等节点被视为私有 LAN 本地网络,用于仅限本地的服务访问;父路由 `/dashboard/context` → `/dashboard/context/settings` 重定向停止 RSC 预取 404;`/api/v1/providers/{cliproxyapi,9router}/models` 返回同步的内嵌服务模型而非 `invalid_provider`。([#5299](https://github.com/diegosouzapw/OmniRoute/pull/5299), #5298 — 感谢 @KooshaPari) +- **thinking (claude):** 修复 OpenAI 兼容路径(Cursor → Claude OAuth)上 Claude 自适应思考的三个独立缺陷。**(A)** 仪表盘思考预算设置在每次重启时被丢弃——`setThinkingBudgetConfig` 从未在启动时调用,因此保存的 `{mode:"adaptive"…}` 静默回退到透传模式;现在在 `server-init` 中从设置中恢复。**(B)** Claude 执行器在翻译_之后_强制注入自适应思考,忽略了运维人员的预算——现在遵循 `mode:"auto"`(剥离),同时保持默认(透传)行为逐字节不变,因此原生 Claude Code 不受影响,并将运维人员的 `thinking.type:"enabled"` 重新映射为 Opus 4.7/4.8 所需的 `adaptive` 形状(`enabled` → 400)。**(D)** 在回放时,无签名的 `reasoning_content` 被重建为携带伪造签名的 `thinking` 块 → Anthropic `400 "Invalid signature in thinking block"`;现在发出无签名的 `redacted_thinking` 块(真实签名仍然逐字保留)。回归测试:`tests/unit/thinking-budget-hydration-5312.test.ts`、`base-thinking-budget-config-5312.test.ts`、`openai-to-claude-redacted-replay-5312.test.ts`(现有 #5123/#4479/#2454 套件仍通过)。`` 内容标记通道不匹配(RC-C,与 #5245 共享)作为后续任务待 Anthropic 在线验证。([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312) — 感谢 @vitalNohj) +- **opencode (proxy pool):** OpenCode Free 每账户代理模态框现在提供全局**代理池下拉菜单**(按 ID 引用),而不是强制在每个账户上手动填写 Host/Port/凭证——#5217 缺口 1。**已保存/自定义**切换开关:"已保存"从 `GET /api/settings/proxies` 中选择预保存的代理并存储 `{fingerprint, proxyId}`,因此更新该池代理会应用到使用它的所有账户;"自定义"保留手动输入(内联存储)作为退路。解析在服务端进行(`resolveAccountProxiesFromRegistry`),因此执行器仍然收到不变的已解析代理;现有的内联条目继续有效,未知/已删除的 `proxyId` 安全降级为直连。回归测试:`tests/unit/noauth-proxy-resolution.test.ts`、`tests/unit/ui/noauth-account-card.test.tsx`。([#5217](https://github.com/diegosouzapw/OmniRoute/issues/5217) 缺口 1 — 感谢 @daniij) +- **thinking (claude):** 让原生 reasoning_content 客户端(如 Cursor)可以退出 `` 关闭标记,使其不再将孤立的 `` 泄漏到可见的 `content` 中(#5312 的 RC-C,与 #5245 共享)。标记抑制机制已存在(UA 白名单,#5348),但 Cursor 的 UA 被有意排除在外;此次添加了显式请求头 `x-omniroute-thinking-marker: off`(也支持 `on`/`keep` 强制保留)来覆盖 UA 策略。未携带此头时行为逐字节不变——扫描 `content` 中标记的 Claude Code/Cursor-composer 客户端(#4633)仍然会收到它。回归测试:`tests/unit/think-close-marker-suppress-5245.test.ts`(#5123 case-b + #4479 仍通过)。([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312), [#5245](https://github.com/diegosouzapw/OmniRoute/issues/5245) — 感谢 @vitalNohj, @wild-feather) +- **cors:** 浏览器/Electron 客户端(如 Wayland AI)现在可以开箱即用地将 OmniRoute 作为 OpenAI 兼容的服务商使用。Token 认证的 API 面(`/v1/*`、`/v1beta/*`)现在默认返回宽松的 `Access-Control-Allow-Origin`(回显请求中的 `Origin`,无时使用 `*`)——与 9router 和 OpenAI 兼容生态系统保持一致——因此渲染进程的 `fetch` 可以读取响应,而不是因 CORS 阻止而失败为"站点未找到"/空目录(而 `curl` 不发送预检请求,可以正常工作)。这是**安全的**:这些路由通过浏览器永远不会自动附加的 `Authorization`/`x-api-key` 头进行认证(无凭证会话/CSRF 暴露),并且 `Access-Control-Allow-Credentials` 永远不与回显/通配符配对。Cookie 认证的 **MANAGEMENT/dashboard 路由保持严格失败关闭**;`CORS_ALLOW_ALL`/`CORS_ALLOWED_ORIGINS` 仍然优先。回归测试:`tests/unit/cors/origins.test.ts`、`tests/unit/authz/pipeline.test.ts`。(Bug 2 [#5242](https://github.com/diegosouzapw/OmniRoute/issues/5242) — 感谢 @jonlwheat2-gif) +- **grok-web:** 转发 Cloudflare 验证 Cookie,停止将 IP 信誉拦截误标为错误 Cookie。"检测 Cookie"即使使用有效的完整浏览器会话也返回 `Invalid SSO cookie`——但 Cookie 解析器从来没有问题(它能从完整的 DevTools 头中稳健提取 `sso`/`sso-rw`)。修复了两个实际问题:**(1)** `buildGrokCookieHeader` 现在粘贴时会转发 `cf_clearance` 和 `__cf_bm`(之前会丢弃它们;AIClient2API 也会转发)——严格增量,裸 `sso` blob 仍然产生完全相同的 `sso=…`;**(2)** 当用户提供了 `cf_clearance` 时,来自 grok.com 的 401 / invalid-credentials-403 现在会显示为 IP 信誉/反机器人拦截(cf_clearance 与 IP+TLS+UA 绑定,无法从不同机器重放),而不是误导性的"无效 SSO Cookie——请重新粘贴"。没有 clearance 的裸 Cookie 仍然获得重新粘贴提示。回归测试:`web-cookie-auth.test.ts` + `provider-validation-specialty.test.ts`。([#5350](https://github.com/diegosouzapw/OmniRoute/issues/5350) — 感谢 @SeaXen) +- **cli (serve):** `omniroute serve` 可选的原生 **HTTPS/TLS**——使严格 CSP 的 Electron 应用和浏览器可以通过 `https://` 访问 OmniRoute,而非纯 `http://localhost`。提供 `--tls-cert --tls-key `(或 `OMNIROUTE_TLS_CERT`/`OMNIROUTE_TLS_KEY`),独立服务器在同一监听端口上终止 TLS(无需额外端口/代理);WebSocket 升级(实时仪表盘 + `/v1` 流式传输)通过 `wss://` 正常工作,因为 `https.Server extends http.Server`。无 TLS 标志时 HTTP 路径与之前逐字节相同;仅提供 cert 或 key 其中之一、或路径不可读时,记录警告并保持 HTTP(永不会半启用,永不会崩溃)。本地主机的自动生成自签名证书作为后续任务;目前请提供显式的 cert/key(或在 OmniRoute 前部署 TLS 终结器)。回归测试:`tests/unit/tls-options.test.ts`。(Bug 1C [#5242](https://github.com/diegosouzapw/OmniRoute/issues/5242) — 感谢 @jonlwheat2-gif) +- **opencode/observability:** 使 OpenCode Free 账户/代理轮换可见,并修复两个伴随发现的实际缺陷。**(1)** 每请求轮换选择日志(`dispatch via account … through proxy …`)级别为 `debug`(在默认 `APP_LOG_LEVEL=info` 下被隐藏)——提升为 `info`,使洗牌/冷却生命周期可审核(Token 保持脱敏)。**(2)** `[ProxyEgress]` 即使应用了账户代理也报告 `proxy=direct`,因为出口日志器位于执行器的嵌套代理上下文之外运行——现在有效应用的代理通过线程化到 proxy AsyncLocalStorage 的应用代理接收器来捕获,并反映在出口日志中。**(3)** `[callLogs] too many SQL variables`——`deleteCallLogRowsByIds` 在单个 `IN (…)` 中删除多达 5000 个 ID,超过 SQLite 的 ~999 绑定参数上限,导致日志裁剪/保留操作中断;ID 现在分块处理(每语句 ≤500)。回归测试:`tests/unit/call-log-trim-sql-vars-5217.test.ts`、`apply-executor-proxy-info-5217.test.ts`、扩展的 `opencode-proxy-rotation-4954.test.ts`。([#5217](https://github.com/diegosouzapw/OmniRoute/issues/5217) — 感谢 @daniij) +- **chatgpt-web:** 将工具/函数调用接入 `chatgpt-web` 服务商。它是唯一从未读取 `body.tools` 的 Web 会话执行器——两个响应构建器都硬编码 `finish_reason:"stop"` 并仅输出内容,因此工具调用被静默丢弃(模型用散文回答)。现在使用共享的 `webTools` 提示模拟适配层(`` 约定的 system 消息 + `{…}` 响应解析),完全与其他 9 个同级执行器一致(qwen-web、perplexity-web 等)——它只是被 #3259 发布所遗漏。工具模式缓冲并输出 `tool_calls` + `finish_reason:"tool_calls"`(与图像生成路径隔离);纯聊天不变。回归测试:`tests/unit/chatgpt-web-tools-5240.test.ts`。([#5240](https://github.com/diegosouzapw/OmniRoute/issues/5240) — 感谢 @Rougler) +- **oauth/dashboard:** 修复持久化/错误的 Antigravity "Token 已过期"徽章(#3679/#3850 的延续)。两个原因:**(1)** 新的 OAuth 连接从未设置 `tokenExpiresAt`(仅 `expiresAt`),因此仪表盘徽章——优先使用 `tokenExpiresAt || expiresAt`——回退到原始授权时钟,可能在首次后台刷新前闪烁错误的"Token 已过期"。现在创建时在所有 5 个 OAuth 创建路径中都将 `expiresAt` 镜像到 `tokenExpiresAt`(共享的 `buildOAuthConnectionCreatePayload`),与每个已经写入两者的刷新路径保持一致。**(2)** 当可刷新的连接没有可用的刷新 Token 时,健康检查扫描会静默跳过它,导致 `testStatus="active"` 永远保持而外观徽章却显示过期;现在会输出终止性的 `testStatus="expired"`("需要重新认证"),严格设置门控,永不会覆盖不可刷新的服务商或已终止/冷却状态。回归测试:`tests/unit/oauth-connection-tokenexpiresat-5326.test.ts`、`tests/unit/token-health-no-refresh-token-expired-5326.test.ts`。([#5326](https://github.com/diegosouzapw/OmniRoute/issues/5326)) +- **routing:** 对 API 密钥轮询连接(一个连接中有多个密钥的 `extraApiKeys`)在遇到上游 `402 "Insufficient account balance"` 时自动禁用已耗尽的 API Key。每个连接的路径已经将 402 终止化(→ `credits_exhausted`),但每个 KEY 的健康跟踪器(`recordKeyHealthStatus`)仅记录 `401` 的失败,因此 402 耗尽的密钥仍留在轮换中并不断重试。现在 402 通过新的 `recordKeyTerminal` 将当前密钥立即标记为无效(终止态——余额在会话中间不会恢复),因此轮换器跳过它并切换到下一个健康密钥;状态跨重启持久化。同时还向余额耗尽体信号中添加了 `insufficient balance`/`insufficient_balance`/`insufficient account balance`,使非 402 的余额不足响应也能终止化。回归测试:`tests/unit/key-health-402-disable-5239.test.ts`。([#5239](https://github.com/diegosouzapw/OmniRoute/issues/5239) — 感谢 @muflifadla38) +- **cli:** `omniroute serve` 不再丢弃用户设置的 `NODE_OPTIONS=--max-old-space-size=…`。之前无条件地覆盖 `NODE_OPTIONS`(并传递显式的 `--max-old-space-size` CLI 参数)使用校准默认值,导致导出 `--max-old-space-size=8192` 的用户仍然以旧的限制运行并发生 OOM(#5238 报告者设置 8192,在约 505MB 时崩溃)。现在与 Electron 和独立启动器行为一致:如果 `NODE_OPTIONS` 已经固定了堆内存,该值优先(并抑制重复的 CLI 参数);否则追加校准的 `--max-old-space-size`,保留不相关的标志。回归测试:`tests/unit/serve-node-options-preserve-5238.test.ts`。(缺陷 C [#5238](https://github.com/diegosouzapw/OmniRoute/issues/5238);`b.mask`/OOM-root 部分单独跟踪。) +- **dashboard:** 恢复服务商的**可用模型**工具栏中的 `{active}/{total} active` 模型数量徽章(服务商详情页)。它在 v3.8.13 的神文件分解(#3327)中被删除——`ModelVisibilityToolbar` 仍然接收 `activeCount`/`totalCount`,但它们被孤立为未使用的 `_` 前缀参数,渲染 `` 从未被迁移过来(`modelsActiveCount` i18n 键仍然保留)。重新连接现有 props 到现有键;零数据层或 i18n 更改。回归测试:`modelVisibilityToolbarActiveCount.test.tsx`。([#5264](https://github.com/diegosouzapw/OmniRoute/issues/5264)) +- **rerank:** `/v1/rerank` 不再以 `400 "Invalid rerank model"` 拒绝 SiliconFlow 和 DeepInfra 的 Qwen3-Reranker 模型,即使 `/v1/models` 已列出它们。模型 ID 解析器从来不是问题(它已经在第一个斜杠处分割,因此 `siliconflow/Qwen/Qwen3-Reranker-8B` 解析正确)——`siliconflow` 和 `deepinfra` 只是缺失了 rerank 服务商注册。已添加两者:SiliconFlow 作为 Cohere 兼容,DeepInfra 通过新的 `deepinfra` 适配器(模型在 URL 路径中 `POST /v1/inference/`,`{queries,documents}` 请求,位置 `{scores}` 响应映射为 Cohere `results[]`)。回归测试:`tests/unit/rerank-providers-5332.test.ts`。([#5332](https://github.com/diegosouzapw/OmniRoute/issues/5332) — 感谢 @maikokan) +- **authz/dashboard:** 当通过局域网 IP/非 localhost 主机访问仪表盘时,不再以 `403 INVALID_ORIGIN` 拒绝所有仪表盘变更操作。源绑定检查(#5278)只接受配置的 `*_PUBLIC_BASE_URL`(通常为 `http://localhost:20128`)加上内部的 `request.url` 源——而 Next.js standalone 报告的是绑定主机而非真实的 `Host`。因此在 `http://192.168.0.15:20128` 打开仪表盘会使浏览器的同源 `Origin` 无法匹配任何候选项,导致**所有** POST/PUT/DELETE(保存 API Key、保存服务商、测试连接)都失败,而 GET 仍能正常工作。两个修复:**(a)** 请求 `Host`(或受信任的 `X-Forwarded-Host`)现在被接受为有效的变更操作源,由两个独立检查控制——Token 标记的 socket 对端必须是 loopback/私有局域网**且** Host 本身也必须是 loopback/私有局域网 IP 字面量,因此 DNS 重绑定域名(分类为 `remote`)永远不能成为受信任的源,协议绑定到实际连接;(b) `INVALID_ORIGIN` 响应现在携带可操作的消息(设置 `OMNIROUTE_PUBLIC_BASE_URL`),仪表盘通过共享的 `extractApiErrorMessage` 辅助函数显示 API 错误 `.message`,而不是渲染原始错误对象。回归测试:`tests/unit/authz/public-origin.test.ts`(直接 LAN/loopback + DNS 重绑定防御)、`tests/unit/api-error-message-5340.test.ts`。([#5340](https://github.com/diegosouzapw/OmniRoute/issues/5340)) -### 📝 Maintenance +### 📝 维护 -- **chore(dead-code):** repo-wide sweep of unused exported symbols and a matching dead-code baseline ratchet — trimmed unused exported helpers, validation/settings/encryption-config schemas, utility/domain/static-constant/formatting helpers, runtime test helpers, the request-timeout fetch wrapper, event-bus, semantic-cache (maintenance + expiry), correlation-middleware, MCP-scope, service-registry, build-profile, api-key-format, authz-class, models.dev-context, embedding-cache, provider-limits-scheduler, search-validator, webhook-example, agent-skills-repo-URL and command-code-auth-cleanup exports. Pure dead-code removal validated by `typecheck:core` (no remaining referencing site) — no behavior change. ([#5321](https://github.com/diegosouzapw/OmniRoute/pull/5321), [#5322](https://github.com/diegosouzapw/OmniRoute/pull/5322), [#5324](https://github.com/diegosouzapw/OmniRoute/pull/5324), [#5325](https://github.com/diegosouzapw/OmniRoute/pull/5325), [#5328](https://github.com/diegosouzapw/OmniRoute/pull/5328), [#5329](https://github.com/diegosouzapw/OmniRoute/pull/5329), [#5330](https://github.com/diegosouzapw/OmniRoute/pull/5330), [#5331](https://github.com/diegosouzapw/OmniRoute/pull/5331), [#5333](https://github.com/diegosouzapw/OmniRoute/pull/5333), [#5334](https://github.com/diegosouzapw/OmniRoute/pull/5334), [#5335](https://github.com/diegosouzapw/OmniRoute/pull/5335), [#5336](https://github.com/diegosouzapw/OmniRoute/pull/5336), [#5338](https://github.com/diegosouzapw/OmniRoute/pull/5338), [#5339](https://github.com/diegosouzapw/OmniRoute/pull/5339), [#5353](https://github.com/diegosouzapw/OmniRoute/pull/5353), [#5354](https://github.com/diegosouzapw/OmniRoute/pull/5354), [#5355](https://github.com/diegosouzapw/OmniRoute/pull/5355), [#5356](https://github.com/diegosouzapw/OmniRoute/pull/5356), [#5357](https://github.com/diegosouzapw/OmniRoute/pull/5357), [#5359](https://github.com/diegosouzapw/OmniRoute/pull/5359), [#5362](https://github.com/diegosouzapw/OmniRoute/pull/5362), [#5364](https://github.com/diegosouzapw/OmniRoute/pull/5364), [#5365](https://github.com/diegosouzapw/OmniRoute/pull/5365), [#5366](https://github.com/diegosouzapw/OmniRoute/pull/5366), [#5368](https://github.com/diegosouzapw/OmniRoute/pull/5368), [#5369](https://github.com/diegosouzapw/OmniRoute/pull/5369), [#5371](https://github.com/diegosouzapw/OmniRoute/pull/5371) — thanks @JxnLexn) +- **chore(dead-code):** 全仓库清理未使用的导出符号,并设置对应的死代码基线递增门禁 — 修剪了未使用的导出辅助函数、校验/设置/加密配置 Schema、工具/domain/静态常量/格式化辅助函数、运行时测试辅助函数、请求超时 fetch 包装器、event-bus、semantic-cache(维护 + 过期)、correlation-middleware、MCP-scope、service-registry、build-profile、api-key-format、authz-class、models.dev-context、embedding-cache、provider-limits-scheduler、search-validator、webhook-example、agent-skills-repo-URL 和 command-code-auth-cleanup 导出。纯死代码移除,由 `typecheck:core` 验证(无剩余引用点)——无行为变更。([#5321](https://github.com/diegosouzapw/OmniRoute/pull/5321), [#5322](https://github.com/diegosouzapw/OmniRoute/pull/5322), [#5324](https://github.com/diegosouzapw/OmniRoute/pull/5324), [#5325](https://github.com/diegosouzapw/OmniRoute/pull/5325), [#5328](https://github.com/diegosouzapw/OmniRoute/pull/5328), [#5329](https://github.com/diegosouzapw/OmniRoute/pull/5329), [#5330](https://github.com/diegosouzapw/OmniRoute/pull/5330), [#5331](https://github.com/diegosouzapw/OmniRoute/pull/5331), [#5333](https://github.com/diegosouzapw/OmniRoute/pull/5333), [#5334](https://github.com/diegosouzapw/OmniRoute/pull/5334), [#5335](https://github.com/diegosouzapw/OmniRoute/pull/5335), [#5336](https://github.com/diegosouzapw/OmniRoute/pull/5336), [#5338](https://github.com/diegosouzapw/OmniRoute/pull/5338), [#5339](https://github.com/diegosouzapw/OmniRoute/pull/5339), [#5353](https://github.com/diegosouzapw/OmniRoute/pull/5353), [#5354](https://github.com/diegosouzapw/OmniRoute/pull/5354), [#5355](https://github.com/diegosouzapw/OmniRoute/pull/5355), [#5356](https://github.com/diegosouzapw/OmniRoute/pull/5356), [#5357](https://github.com/diegosouzapw/OmniRoute/pull/5357), [#5359](https://github.com/diegosouzapw/OmniRoute/pull/5359), [#5362](https://github.com/diegosouzapw/OmniRoute/pull/5362) — 感谢 @JxnLexn / @diegosouzapw) --- @@ -488,556 +506,556 @@ _In development — bullets added per PR; finalized at release._ ## [3.8.39] — 2026-06-28 -### ✨ New Features +### ✨ 新功能 -- **feat(oauth): remote Antigravity login via local helper + paste-credentials** — Antigravity (and other Google "native/desktop" OAuth providers) use Google's `firstparty/nativeapp` consent, which only releases the auth code when the loopback redirect (`127.0.0.1:`) is reachable from the approving browser. On a remote VPS install that loopback lives on the server, so the consent hangs forever and never emits a code — the "paste the callback URL" fallback has nothing to paste (a Google-side constraint, identical in upstream 9router). A new `omniroute login antigravity` CLI helper runs the OAuth on the user's **own** machine (where 127.0.0.1 works), exchanges the code, and prints a single-line `omniroute-cred-v1.…` credential blob; the dashboard's Antigravity Connect → Step 2 field now accepts that blob (alongside callback URLs) and persists the connection via a new `paste-credentials` action (server-side onboarding, provider-allowlisted, with the blob's embedded provider required to match the route). The SSH local-forward tunnel is documented as a zero-tooling alternative. See [`docs/guides/REMOTE-MODE.md`](docs/guides/REMOTE-MODE.md). ([#5203](https://github.com/diegosouzapw/OmniRoute/pull/5203)) -- **feat(agent-bridge): graceful cert-install fallback for containers / headless** — when the MITM root CA can't be installed into the system trust store automatically (Docker / headless / no sudo / read-only trust store), the Agent Bridge no longer hard-fails on start with a generic "Certificate install failed". It now starts in skip mode and the dashboard surfaces a platform-specific **manual-install guide** (plus a CA download link) so the operator can trust the certificate by hand. The trust-cert endpoints return a structured `{ skippable, manualGuide }` response (HTTP 200) for environment failures instead of a 500; an explicit user cancellation is still reported distinctly. ([#4546](https://github.com/diegosouzapw/OmniRoute/issues/4546) — thanks @phuchptty) -- **feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat)** — extends the `omniroute_ccr_retrieve` MCP tool and `/api/compression/retrieve` endpoint with optional `range` (byte/line slice), `grep` (ReDoS-safe literal or bounded-pattern match against stored lines), and `stats` (byte/line/word counts) parameters so agents pull exactly the slice or summary they need instead of re-expanding the entire stored block. All parameters are optional — no parameters returns the full block byte-identical to the existing behavior; the CCR store written by ionizer/fuzzy/headroom is fully compatible. Sixth item of the compression roadmap. ([#5187](https://github.com/diegosouzapw/OmniRoute/pull/5187)) -- **feat(compression): TOON best-of-N candidate encoder + encoder A/B table** — adds `@toon-format/toon` as a candidate encoder in the headroom compression engine via a best-of-N scheme: both GCF and TOON run per prompt and the shorter result is kept, rather than hard-swapping encoders (GCF already encodes the headroom block and TOON is not a lossless universal win). An encoder A/B comparison table (GCF vs TOON vs JSON — bytes and cl100k tokens) is now surfaced in the compression studio. Fifth item of the compression feature-extraction roadmap (bench: [#5080](https://github.com/diegosouzapw/OmniRoute/pull/5080), gate: [#5127](https://github.com/diegosouzapw/OmniRoute/pull/5127), fuzzy/gate: [#5143](https://github.com/diegosouzapw/OmniRoute/pull/5143), ionizer: [#5148](https://github.com/diegosouzapw/OmniRoute/pull/5148)). ([#5163](https://github.com/diegosouzapw/OmniRoute/pull/5163)) +- **feat(oauth): 远程 Antigravity 登录 —— 本地助手 + 粘贴凭证** — Antigravity(及其他 Google "原生/桌面" OAuth 服务商)使用 Google 的 `firstparty/nativeapp` 授权页面,只有当 loopback 重定向(`127.0.0.1:`)可从授权浏览器访问时才释放认证码。在远程 VPS 安装中,该 loopback 存在于服务器上,因此授权页面永远挂起,从不发出认证码——"粘贴回调 URL"的退路方案无法使用(这是 Google 端的限制,上游 9router 同样存在)。新增 `omniroute login antigravity` CLI 助手在用户**本地**机器上运行 OAuth(127.0.0.1 可用),交换认证码,并打印一行 `omniroute-cred-v1.…` 凭证 blob;仪表盘的 Antigravity 连接 → 第 2 步字段现在接受该 blob(以及回调 URL),并通过新的 `paste-credentials` 操作持久化该连接(服务端引导,服务商白名单化,blob 中内嵌的服务商必须与路由匹配)。SSH 本地转发隧道作为零工具替代方案已被文档化。参见 [`docs/guides/REMOTE-MODE.md`](docs/guides/REMOTE-MODE.md)。([#5203](https://github.com/diegosouzapw/OmniRoute/pull/5203)) +- **feat(agent-bridge): 容器/无头环境下的优雅证书安装回退** — 当 MITM 根 CA 无法自动安装到系统信任存储时(Docker / 无头 / 无 sudo / 只读信任存储),Agent Bridge 不再在启动时以通用"Certificate install failed"硬失败。它现在以跳过模式启动,仪表盘展示平台特定的**手动安装指南**(加上 CA 下载链接),运维人员可以手动信任证书。([#4546](https://github.com/diegosouzapw/OmniRoute/issues/4546) — 感谢 @phuchptty) +- **feat(compression): CCR 范围/grep/统计检索(ReDoS 安全、向后兼容)** — 为 `omniroute_ccr_retrieve` MCP 工具和 `/api/compression/retrieve` 端点扩展 `range`(字节/行切片)、`grep`(ReDoS 安全的字面量或有界模式匹配)和 `stats`(字节/行/词计数)参数,代理可以精确获取需要的切片或摘要。所有参数均可选——无参数时返回完整块,行为与现有行为逐字节相同;CCR 存储完全兼容。压缩路线图第六项。([#5187](https://github.com/diegosouzapw/OmniRoute/pull/5187)) +- **feat(compression): TOON 最优 N 候选编码器 + 编码器 A/B 对比表** — 将 `@toon-format/toon` 作为候选编码器通过最优 N 方案添加到 headroom 压缩引擎:每个提示同时运行 GCF 和 TOON,保留较短结果。压缩工作室中展示编码器 A/B 对比表(GCF/TOON/JSON——字节数和 cl100k Token)。压缩特性提取路线图第五项。([#5163](https://github.com/diegosouzapw/OmniRoute/pull/5163)) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **fix(oauth): Antigravity refresh no longer nulls the stored refresh_token on an empty upstream response** — Google's OAuth token endpoint uses non-rotating refresh tokens: a refresh response normally OMITS `refresh_token` and occasionally returns it as an empty string. The Antigravity executor's `refreshCredentials` used `typeof tokens.refresh_token === "string" ? tokens.refresh_token : credentials.refreshToken`, and because `typeof "" === "string"` is true, an empty-string response overwrote the good token with `""` — nulling it on first refresh. The check now treats a non-string **or empty** value as absent and preserves the stored token, matching the canonical `refreshGoogleToken` (`tokens.refresh_token || refreshToken`) semantics. ([#3850](https://github.com/diegosouzapw/OmniRoute/issues/3850) — thanks @3xa228148) -- **fix(api): LAN/Tailscale dashboard access — `ws:` CSP scheme, GET-exempt version route, surface combo field errors** — three failures when opening the dashboard from a non-loopback host: (1) CSP `connect-src` allowed the `ws:` scheme only for loopback origins, blocking the dashboard's `ws://:*` Live WebSocket from LAN/Tailscale clients; the bare `ws:` scheme is now permitted (symmetric with the bare `wss:` already allowed), kept declarative in `next.config.mjs` with no global middleware (the project has none by design); (2) `GET /api/system/version` was blocked by `LOCAL_ONLY_API_PREFIXES` for all methods despite only `POST` spawning child processes (git/npm/pm2) — a new `LOCAL_ONLY_API_GET_EXEMPTIONS` set exempts safe read methods for this path while keeping `POST`/`PUT`/`PATCH`/`DELETE` strictly loopback-only; (3) `COMBO_002` validation errors only surfaced the generic message — `firstField`/`firstMessage` are now extracted from the first Zod issue and included in the response body. ([#5083](https://github.com/diegosouzapw/OmniRoute/issues/5083) — thanks @KooshaPari for the diagnosis and original PR #5084) -- **fix(sse): defer `` close so it never leaks before `tool_calls` in Claude→OpenAI streaming** — when a Claude thinking block was followed by a tool_use block, the translator unconditionally emitted a `content: ""` chunk at `content_block_stop`, injecting a spurious assistant text chunk immediately before the `tool_calls` delta and corrupting OpenAI-compatible clients (e.g. Kimi Coding). The close marker is now deferred: it is flushed at the first `text_delta` that follows the thinking block (preserving the #4633 / decolua/9router#454 behavior for Claude Code / Cursor) or at stream finish when no tool_calls were collected. Tool-use streams never get a `text_delta` after the thinking block, so `` is never emitted into content before `tool_calls`. ([#5123](https://github.com/diegosouzapw/OmniRoute/issues/5123)) -- **fix(sse): normalize array user-message content in the Command Code executor to prevent upstream 400** — when a client sends a user turn whose `content` is an array of content parts (e.g. `[{type:"text",text:"…"}, …]`), the raw array was forwarded verbatim to the Command Code upstream, which requires `messages[N].content` for the `user` role to be a plain string — resulting in `expected string, received array` / HTTP 400 on DeepSeek V4-Pro and other Command Code models. The user branch of `convertMessages` now calls `normalizeContentText()` (already used by system, assistant, and tool branches) so multi-part user content is joined to a string before dispatch. Partially addresses ([#5166](https://github.com/diegosouzapw/OmniRoute/issues/5166)); the 0-output-token symptom on reasoning-only models is tracked separately. -- **fix(mcp): return HTTP 404 (not 400) for an unknown/expired Streamable HTTP session id** — when an MCP session is terminated or idles out and the client reuses the stale `Mcp-Session-Id` header, the Streamable HTTP transport replied with HTTP 400. The MCP spec (2025-03-26 and 2025-11-25, Session Management) mandates HTTP 404 Not Found in that case, and spec-compliant clients only re-initialize a session on 404 — so the 400 was non-recoverable. The handler now returns 404 for a present-but-unknown session id, while a _missing_ session id on a non-initialize request correctly stays 400. ([#5169](https://github.com/diegosouzapw/OmniRoute/issues/5169) — thanks @czer323) -- **fix(api): blocking "Auto (Zero-Config)" in Security settings now removes `auto/*` from `/v1/models`** — the built-in `auto/*` combo advertiser (#4164 / #4235) at the top of the models catalog ignored `settings.blockedProviders`, so checking **Auto (Zero-Config)** under Security → Blocked Providers had no effect and the model picker kept listing every `auto/*` entry. The injection loop now skips the entire `auto/*` block when the system provider `auto` (its id and alias are both `auto`) is blocked, consistent with how every other provider is filtered from the catalog. ([#5192](https://github.com/diegosouzapw/OmniRoute/issues/5192) — thanks @WslzGmzs) -- **fix(cli): auto-calibrate the server V8 heap from physical RAM instead of a fixed 512MB default** — the server was spawned with a hard-coded `--max-old-space-size=512` (`omniroute serve`) or with no heap flag at all (Electron desktop, which then inherited the runtime's low ~512MB default), so RAM-rich machines still OOM-crashed under load (`FATAL ERROR: Ineffective mark-compacts near heap limit … ~500MB` at code=134) with many providers/accounts and large model catalogs (one report: 16GB RAM, 65 providers, ~100 accounts, ~2600 models). A new `calibrateHeapFallbackMb(os.totalmem())` helper derives the default heap as ~35% of physical RAM, clamped to `[512, 4096]`, and is wired into both `bin/cli/commands/serve.mjs` and `electron/main.js`. An explicit `OMNIROUTE_MEMORY_MB` (or a pre-set `--max-old-space-size`) still wins, so the #2939 override contract is unchanged. ([#5172](https://github.com/diegosouzapw/OmniRoute/issues/5172), [#5160](https://github.com/diegosouzapw/OmniRoute/issues/5160), [#5152](https://github.com/diegosouzapw/OmniRoute/issues/5152) — thanks @manchairwang, @Xyzjesus) +- **fix(oauth): Antigravity 刷新不再因上游空响应将存储的 refresh_token 置空** — Google 的 OAuth Token 端点使用非轮换刷新 Token:刷新响应通常不包含 `refresh_token`,偶尔会返回空字符串。Antigravity 执行器的 `refreshCredentials` 使用了 `typeof tokens.refresh_token === "string" ? tokens.refresh_token : credentials.refreshToken`,而 `typeof "" === "string"` 为真,因此空字符串响应将有效 Token 覆盖为空——首次刷新即将其置空。现在将非字符串**或空值**视为缺失,保留存储的 Token,与规范的 `refreshGoogleToken`(`tokens.refresh_token || refreshToken`)语义一致。([#3850](https://github.com/diegosouzapw/OmniRoute/issues/3850) — 感谢 @3xa228148) +- **fix(api): LAN/Tailscale 仪表盘访问——`ws:` CSP 方案、版本路由 GET 豁免、展示 combo 字段错误** — 从非 loopback 主机打开仪表盘时的三个故障:(1) CSP `connect-src` 仅对 loopback 源允许 `ws:` 方案,阻止了 LAN/Tailscale 客户端的实时 WebSocket 连接;现在允许裸 `ws:`(与已允许的 `wss:` 对称);(2) `GET /api/system/version` 被 `LOCAL_ONLY_API_PREFIXES` 阻止——新增豁免白名单;(3) `COMBO_002` 校验错误现在在响应中包含首个 Zod 问题的字段和消息。([#5083](https://github.com/diegosouzapw/OmniRoute/issues/5083) — 感谢 @KooshaPari) +- **fix(sse): 延迟 `` 关闭以防止其在 Claude→OpenAI 流式传输中泄漏到 `tool_calls` 之前** — 思考块后跟 tool_use 块时翻译器会注入伪造的助手文本区块。关闭标记现在推迟到第一个 `text_delta` 或流结束时刷新。([#5123](https://github.com/diegosouzapw/OmniRoute/issues/5123)) +- **fix(sse): 规范化 Command Code 执行器中数组类型的用户消息内容以防止上游 400** — 用户消息 content 为数组时被逐字转发(Command Code 要求字符串),现在调用 `normalizeContentText()` 合并为字符串。([#5166](https://github.com/diegosouzapw/OmniRoute/issues/5166)) +- **fix(mcp): 对未知/过期�� Streamable HTTP 会话 ID 返回 HTTP 404(而非 400)** — MCP 规范要求 404 以便客户端重新初始化。缺少会话 ID 的非初始化请求仍正确返回 400。([#5169](https://github.com/diegosouzapw/OmniRoute/issues/5169) — 感谢 @czer323) +- **fix(api): 安全设置屏蔽"自动(零配置)"现在从 `/v1/models` 移除 `auto/*`** — 内置 `auto/*` 公告商忽略了 `settings.blockedProviders`。注入循环现在在 `auto` 被屏蔽时跳过整个 `auto/*` 块。([#5192](https://github.com/diegosouzapw/OmniRoute/issues/5192) — 感谢 @WslzGmzs) +- **fix(cli): 根据物理 RAM 而非固定 512MB 默认值自动校准服务器 V8 堆内存** — 新 `calibrateHeapFallbackMb` 按物理 RAM ~35% 推导默认堆大小,限制 `[512, 4096]`。`OMNIROUTE_MEMORY_MB` 仍优先。([#5172](https://github.com/diegosouzapw/OmniRoute/issues/5172) — 感谢 @manchairwang, @Xyzjesus) -- **fix(oauth): Antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange** — the dashboard's Antigravity OAuth login spun indefinitely because `postExchange` awaited the `onboardUser` retry loop inline (up to 10 × 5 s per attempt, each fetch with no timeout), blocking the `/exchange` response forever. Matching the upstream 9router web flow: `onboardUser` now runs fire-and-forget in a background task; the `/exchange` endpoint is bounded by a 10 s hard timeout so it always returns; a progress endpoint lets the dashboard poll onboarding completion state. ([#5193](https://github.com/diegosouzapw/OmniRoute/pull/5193)) -- **fix(antigravity): retry Antigravity accounts by quota family before escalating the combo** — when one Antigravity account returns a quota or rate-limit `429` for a Gemini model (e.g. `gemini-3.5-flash-medium`), combo orchestration could prematurely advance to the next combo model instead of trying other eligible Antigravity accounts for the same quota family. Antigravity quota-family awareness is now added to the fallback path so a `429` on one account triggers a bounded same-model retry across other Antigravity accounts sharing that quota bucket before the combo degrades to a lower-tier model. ([#5180](https://github.com/diegosouzapw/OmniRoute/pull/5180) — thanks @Ardem2025) -- **fix(translator): accept Claude Messages shape in the non-stream malformed-200 guard** — when a Claude client (e.g. Claude Code) is routed to a non-Claude provider, the translated non-streaming response body is in Claude Messages shape (`type: "message", content[]`) produced by `convertOpenAINonStreamingToClaude`. `detectMalformedNonStream` only recognized OpenAI `choices[].message` and Responses API `output[]`, so this shape fell through to `empty_choices` → 502. The guard now recognizes the Claude Messages shape: text, tool_use, and thinking blocks carrying a `signature` count as valid output, while a genuinely empty `content: []` is still flagged. ([#5156](https://github.com/diegosouzapw/OmniRoute/pull/5156) — thanks @NomenAK) -- **fix(sse): resolve nameless deepseek-web `` blocks via parameter-schema match** — when `chat.deepseek.com` emits a `` block with no `` child, no JSON body `name`/`type` key, and no tag suffix, every name-resolution path in `extractCall` returned `null` and the raw XML leaked to the client as plain text. A conservative schema-based fallback now compares the block's extracted parameter names against each declared tool's schema keys; if exactly one tool matches, its name is used. Zero or ambiguous (>1) matches still return `null` so no calls are misattributed. ([#5154](https://github.com/diegosouzapw/OmniRoute/issues/5154), [#5173](https://github.com/diegosouzapw/OmniRoute/pull/5173)) -- **fix(stream): normalize provider safety finish reasons to `content_filter`** — Gemini and Antigravity can return safety/prohibited terminal reasons (`SAFETY`, `RECITATION`, `BLOCKLIST`, `PROHIBITED_CONTENT`) that OpenAI-compatible downstream clients do not recognize. A shared finish-reason normalization helper now maps these to the standard `content_filter` value, applied in both the streaming and JSON collection paths for both providers. ([#5197](https://github.com/diegosouzapw/OmniRoute/pull/5197) — thanks @rdself) -- **fix(responses): normalize non-array Responses API `input` before routing** — the OpenAI Responses API accepts `input` as a string, object, or list, but OmniRoute only handled list-shaped payloads; a string or object `input` was silently dropped on the Responses→Chat Completions path. The translator now normalizes `input` to a list before dispatch; the Codex-native Responses path also normalizes before forwarding (preventing upstream `400 Input must be a list`); and the prompt-injection and PII sanitizer extraction paths are guarded against object-valued `input` so security checks do not throw. ([#5204](https://github.com/diegosouzapw/OmniRoute/pull/5204) — thanks @wilsonicdev) -- **fix(zenmux): normalize vendor-prefixed GLM system roles for Z.AI models** — ZenMux exposes Z.AI GLM via vendor-prefixed OpenAI-compatible IDs such as `z-ai/glm-5.2`. The existing GLM detection only matched bare `glm-*`/`glm` ids, so `zenmux/z-ai/glm-5.2` kept system messages in place; Z.AI rejects compressed histories ending with a system turn before `assistant(tool_calls) → tool` sequences. The fix extends GLM detection to cover `z-ai/glm-*` prefixes and routes them through the existing `normalizeSystemRole` path. ([#5158](https://github.com/diegosouzapw/OmniRoute/pull/5158) — thanks @Thinkscape) -- **fix(xai): add OAuth connection test probe + normalize xAI reasoning effort aliases** — xAI rejects unsupported reasoning effort values (`max`, `xhigh`) with HTTP 400 after a provider update; the xAI translator now maps `max` and `xhigh` to `high` before forwarding. Additionally, xAI OAuth connections had no dashboard test configuration, so provider tests returned `"Provider test not supported"`; a dedicated OAuth test probe is now wired for xAI accounts with regression coverage for the effort normalization. ([#5157](https://github.com/diegosouzapw/OmniRoute/pull/5157) — thanks @nguyenxvotanminh3) -- **fix(serve): honour `HOSTNAME` from `.env` instead of hardcoding `0.0.0.0`** — `bin/cli/commands/serve.mjs` spread `process.env` into the child-process environment but immediately overwrote `HOSTNAME` with a literal `"0.0.0.0"`, silently discarding any user-configured bind address even though `HOSTNAME` is documented in `.env.example` and `docs/reference/ENVIRONMENT.md`. `dist/server.js` already read `process.env.HOSTNAME` correctly; only the CLI wrapper was overriding it. The fix applies `process.env.HOSTNAME || "0.0.0.0"` so the env value takes effect. ([#5134](https://github.com/diegosouzapw/OmniRoute/issues/5134), [#5170](https://github.com/diegosouzapw/OmniRoute/pull/5170) — thanks @anki1kr / @Angelo90810) -- **fix(cli): force `NODE_ENV` to match dev/start run mode in the custom Next server** — when `.env.example` ships `NODE_ENV=production`, starting `npm run dev` via `scripts/dev/run-next.mjs` forwarded that value to the programmatic `next()` entry, which — unlike the `next` CLI — does not normalize it to match the run mode. The resulting production flag caused PostCSS to skip Tailwind's CSS transform, surfacing as `Module parse failed: Unexpected character '@'` on `globals.css`. The custom server now explicitly forces `NODE_ENV=development` for the `dev` path and `NODE_ENV=production` for the `start` path regardless of `.env`. ([#5189](https://github.com/diegosouzapw/OmniRoute/pull/5189) — thanks @backryun) -- **fix(cli): raise dev server Node heap limit to 8 GB to prevent OOM** — `npm run dev` crashed with `FATAL ERROR: Ineffective mark-compacts near heap limit — Allocation failed - JavaScript heap out of memory` while compiling heavy dashboard routes because `node scripts/dev/run-next.mjs` ran on V8's ~4 GB default with no `--max-old-space-size` flag. The `dev` npm script now passes `--max-old-space-size=8192` at invocation time (the only point where this flag can be set for that process). ([#5198](https://github.com/diegosouzapw/OmniRoute/pull/5198) — thanks @backryun) -- **fix(cli): re-enable Turbopack as the default `npm run dev` bundler** — PR #4092 forced webpack because an earlier Turbopack 16.2.x panic (`internal error: entered unreachable code: there must be a path to a root` in `turbopack-core/module_graph`) blocked the OmniRoute module graph. That panic no longer reproduces on the pinned Next 16.2.9, so `OMNIROUTE_USE_TURBOPACK` is flipped from `0` to `1` in `.env.example`, aligning it with `docs/reference/ENVIRONMENT.md` which had already documented the default as `1`. ([#5206](https://github.com/diegosouzapw/OmniRoute/pull/5206) — thanks @backryun) -- **fix(auth): allow synthetic no-auth fallback for mimocode** — mimocode connections without explicit credentials were blocked before reaching the executor. The auth layer now permits a synthetic no-auth fallback for the mimocode provider so credential-free access patterns work as intended. ([#5205](https://github.com/diegosouzapw/OmniRoute/pull/5205) — thanks @KooshaPari) -- **fix(combo): reject empty Responses API `output: []` as a fail-over trigger** — a non-streaming Responses API body with `object: "response"` and `output: []` was accepted as a valid HTTP 200 by the combo response-quality validator, allowing a combo target to stop rather than fail over to the next leg. The non-stream validator now inspects Responses-API-shaped bodies before the generic `output` shortcut and rejects an empty `output: []` as `empty_choices`; structural non-empty output (e.g. `function_call`) remains valid. ([#5207](https://github.com/diegosouzapw/OmniRoute/pull/5207) — thanks @KooshaPari) -- **fix(proxy): close cached dispatchers when clearing the proxy cache** — cached proxy and direct-retry dispatchers were not closed on cache clear, leaking open connection handles. The cache-clear path now calls `close()` on all evicted dispatchers; dispatcher cache and lifecycle helpers have been extracted from the oversized proxy-dispatcher module into a dedicated helper for reuse. ([#5202](https://github.com/diegosouzapw/OmniRoute/pull/5202) — thanks @KooshaPari) -- **fix(proxy): coalesce concurrent fast-fail health probes per proxy URL** — under high concurrency each simultaneous request opened its own TCP health probe for the same proxy URL, creating a thundering-herd burst. Concurrent proxy fast-fail checks are now coalesced so only one TCP probe runs per proxy URL at a time; the completed-result health cache is preserved so subsequent same-URL checks return immediately. ([#5109](https://github.com/diegosouzapw/OmniRoute/issues/5109), [#5208](https://github.com/diegosouzapw/OmniRoute/pull/5208) — thanks @KooshaPari) -- **fix(pwa): prefer cached navigation before showing the offline page** — the service worker was too eager to display `/offline` on transient navigation failures. It now caches successful navigation responses and consults the cached route or app shell before falling back to `/offline`; `/offline` remains the final fallback when no cached navigation or app shell exists. ([#5165](https://github.com/diegosouzapw/OmniRoute/issues/5165), [#5209](https://github.com/diegosouzapw/OmniRoute/pull/5209) — thanks @KooshaPari) -- **fix(request-logger): never render a negative percentage in the compression badge** — when every prompt token was compressed (`totalIn = 0, compressed > 0`), the compression pill displayed `(-100%)` because the badge format hard-coded a leading `-` before the percentage value. The badge now omits the negative sign in this case, correctly representing the saving as a positive ratio. ([#5201](https://github.com/diegosouzapw/OmniRoute/pull/5201) — thanks @KooshaPari) -- **fix(dashboard): use amber for home update-step warning icon** — the warning-state icon in the home update steps (`HomePageClient.tsx`) used `text-yellow-500` (Tailwind `#eab308`), which has poor contrast on light backgrounds (~1.9:1, below WCAG AA) and is inconsistent with the `amber` warning convention used by every sibling element in the same component. Switched to `text-amber-500` — a one-line `className` change with no behavior change. ([#5176](https://github.com/diegosouzapw/OmniRoute/pull/5176)) +- **fix(oauth): Antigravity 登录不再卡死——fire-and-forget 引导 + 有界 post-exchange** — `postExchange` 内联等待 `onboardUser` 重试循环导致登录无限旋转。现在 `onboardUser` 后台 fire-and-forget,`/exchange` 受 10 秒硬超时限制。([#5193](https://github.com/diegosouzapw/OmniRoute/pull/5193)) +- **fix(antigravity): 在升级 combo 之前按配额族重试 Antigravity 账户** — 一个账户返回 429 时,combo 编排可能过早升级。现在先在同一配额族的其他账户重试。([#5180](https://github.com/diegosouzapw/OmniRoute/pull/5180) — 感谢 @Ardem2025) +- **fix(translator): 在非流式 malformed-200 防护中接受 Claude Messages 格式** — 当 Claude 客户端路由到非 Claude 服务商时,翻译后的响应体为 Claude Messages 格式,防护层未识别该格式而返回 502。现在可识别 text/tool_use/thinking 块。([#5156](https://github.com/diegosouzapw/OmniRoute/pull/5156) — 感谢 @NomenAK) +- **fix(sse): 通过参数 schema 匹配解析无名称的 deepseek-web `` 块** — 当 `chat.deepseek.com` 发出无名称 `` 块时,基于 schema 的退路方案将提取的参数名与各工具 schema 键进行比较以匹配名称。([#5154](https://github.com/diegosouzapw/OmniRoute/issues/5154)) +- **fix(stream): 将服务商安全 finish_reason 规范化为 `content_filter`** — Gemini/Antigravity 可返回 SAFETY/RECITATION/BLOCKLIST 等,不再被下游识别。现在统一映射为标准 `content_filter`。([#5197](https://github.com/diegosouzapw/OmniRoute/pull/5197) — 感谢 @rdself) +- **fix(responses): 在路由前规范化非数组 Responses API `input`** — OpenAI Responses API 接受 `input` 为字符串、对象或列表,但 OmniRoute 仅处理列表形式的载荷;字符串或对象 `input` 在 Responses→Chat Completions 路径上被静默丢弃。翻译器现在在调度前将 `input` 规范化为列表;Codex 原生 Responses 路径在转发前也进行规范化(防止上游 `400 Input must be a list`);并且提示注入和 PII 脱敏提取路径对对象值 `input` 进行了防护,使安全检查不会抛出异常。([#5204](https://github.com/diegosouzapw/OmniRoute/pull/5204) — 感谢 @wilsonicdev) +- **fix(zenmux): 规范化 Z.AI 模型的服务商前缀 GLM 系统角色** — ZenMux 通过服务商前缀的 OpenAI 兼容 ID(如 `z-ai/glm-5.2`)暴露 Z.AI GLM。现有的 GLM 检测仅匹配裸 `glm-*`/`glm` ID,因此 `zenmux/z-ai/glm-5.2` 保持了系统消息不变;而 Z.AI 会拒绝以系统轮次结束的压缩历史(在 `assistant(tool_calls) → tool` 序列之前)。修复扩展了 GLM 检测以覆盖 `z-ai/glm-*` 前缀,并将其路由到现有的 `normalizeSystemRole` 路径。([#5158](https://github.com/diegosouzapw/OmniRoute/pull/5158) — 感谢 @Thinkscape) +- **fix(xai): 添加 OAuth 连接测试探针 + 规范化 xAI 推理强度别名** — xAI 在服务商更新后对不支持的推理强度值(`max`、`xhigh`)返回 HTTP 400;xAI 翻译器现在在转发前将 `max` 和 `xhigh` 映射为 `high`。此外,xAI OAuth 连接缺少仪表盘测试配置,导致服务商测试返回 `"Provider test not supported"`;现在为 xAI 账户配置了专用的 OAuth 测试探针,并对强度规范化进行了回归覆盖。([#5157](https://github.com/diegosouzapw/OmniRoute/pull/5157) — 感谢 @nguyenxvotanminh3) +- **fix(serve): honour `HOSTNAME` from `.env` instead of hardcoding `0.0.0.0`** — `bin/cli/commands/serve.mjs` spread `process.env` into the child-process environment but immediately overwrote `HOSTNAME` with a literal `"0.0.0.0"`, silently discarding any user-configured bind address even though `HOSTNAME` is documented in `.env.example` and `docs/reference/ENVIRONMENT.md`. `dist/server.js` already read `process.env.HOSTNAME` correctly; only the CLI wrapper was overriding it. The fix applies `process.env.HOSTNAME || "0.0.0.0"` so the env value takes effect. ([#5134](https://github.com/diegosouzapw/OmniRoute/issues/5134), [#5170](https://github.com/diegosouzapw/OmniRoute/pull/5170) — 感谢 @anki1kr / @Angelo90810) +- **fix(cli): 强制 `NODE_ENV` 匹配自定义 Next 服务器中的 dev/start 运行模式** — 当 `.env.example` 设置 `NODE_ENV=production` 时,通过 `scripts/dev/run-next.mjs` 启动 `npm run dev` 会将该值转发给编程式 `next()` 入口,而该入口——为 `next` CLI 不同——不会将其规范化为匹配运行模式。由此产生的 production 标志导致 PostCSS 跳过 Tailwind 的 CSS 转换,在 `globals.css` 上表现为 `Module parse failed: Unexpected character @ sign`。自定义服务器现在显式强制 `dev` 路径使用 `NODE_ENV=development`,`start` 路径使用 `NODE_ENV=production`,忽略 `.env`。([#5189](https://github.com/diegosouzapw/OmniRoute/pull/5189) — 感谢 @backryun) +- **fix(cli): 将开发服务器 Node 堆内存限制提升至 8 GB 以防止 OOM** — `npm run dev` 在编译重型仪表盘路由时因 `node scripts/dev/run-next.mjs` 使用 V8 默认的约 4 GB 堆内存(无 `--max-old-space-size` 标志)而崩溃,报错 `FATAL ERROR: Ineffective mark-compacts near heap limit — Allocation failed - JavaScript heap out of memory`。`dev` npm 脚本现在在调用时传递 `--max-old-space-size=8192`(这是为该进程设置此标志的唯一时机)。([#5198](https://github.com/diegosouzapw/OmniRoute/pull/5198) — 感谢 @backryun) +- **fix(cli): re-enable Turbopack as the default `npm run dev` bundler** — PR #4092 forced webpack because an earlier Turbopack 16.2.x panic (`internal error: entered unreachable code: there must be a path to a root` in `turbopack-core/module_graph`) blocked the OmniRoute module graph. That panic no longer reproduces on the pinned Next 16.2.9, so `OMNIROUTE_USE_TURBOPACK` is flipped from `0` to `1` in `.env.example`, aligning it with `docs/reference/ENVIRONMENT.md` which had already documented the default as `1`. ([#5206](https://github.com/diegosouzapw/OmniRoute/pull/5206) — 感谢 @backryun) +- **fix(auth): 允许 mimocode 使用合成无认证回退** — mimocode 连接在无显式凭证时会在到达执行器之前被阻止。认证层现在允许 mimocode 服务商的合成无认证回退,使无凭证访问模式按预期工作。([#5205](https://github.com/diegosouzapw/OmniRoute/pull/5205) — 感谢 @KooshaPari) +- **fix(combo): 将空 Responses API `output: []` 视为容灾触发而拒绝** — 非流式 Responses API 体带有 `object: "response"` 和 `output: []` 时,被 Combo 响应质量校验器接受为有效 HTTP 200,导致 Combo 目标停止而非容灾到下一个分支。非流式校验器现在在通用 `output` 快捷方式之前检查 Responses-API 形状的响应体,并将空 `output: []` 拒绝为 `empty_choices`;结构性非空输出(如 `function_call`)仍然有效。([#5207](https://github.com/diegosouzapw/OmniRoute/pull/5207) — 感谢 @KooshaPari) +- **fix(proxy): 在清除代理缓存时关闭缓存的调度器** — 缓存的代理和直连重试调度器在清除缓存时未被关闭,导致连接句柄泄漏。缓存清除路径现在对所有被逐出的调度器调用 `close()`;调度器缓存和生命周期辅助函数已从过度膨胀的 proxy-dispatcher 模块中提取到专用辅助模块以供复用。([#5202](https://github.com/diegosouzapw/OmniRoute/pull/5202) — 感谢 @KooshaPari) +- **fix(proxy): 合并每个代理 URL 的并发快速失败健康探测** — 在高并发下,每个同时到达的请求都会为同一代理 URL 打开自己的 TCP 健康探测,造成惊群效应。并发代理快速失败检查现在被合并,每个代理 URL 同时只运行一个 TCP 探测;已完成的健康结果缓存被保留,使后续相同 URL 的检查立即返回。([#5109](https://github.com/diegosouzapw/OmniRoute/issues/5109), [#5208](https://github.com/diegosouzapw/OmniRoute/pull/5208) — 感谢 @KooshaPari) +- **fix(pwa): 在显示离线页面之前优先使用缓存的导航** — Service Worker 在临时导航失败时过于急切地显示 `/offline`。现在它会缓存成功的导航响应,并在回退到 `/offline` 之前查询缓存的路由或应用壳;`/offline` 仅在无缓存导航或应用壳时作为最终回退保留。([#5165](https://github.com/diegosouzapw/OmniRoute/issues/5165), [#5209](https://github.com/diegosouzapw/OmniRoute/pull/5209) — 感谢 @KooshaPari) +- **fix(request-logger): 永远不在压缩徽章中渲染负百分比** — 当每个提示 Token 都被压缩时(`totalIn = 0, compressed > 0`),压缩药丸徽章显示 `(-100%)`,因为徽章格式在百分比值前硬编码了前导 `-`。徽章现在在这种情况下省略负号,正确地将节省量表示为正向比例。([#5201](https://github.com/diegosouzapw/OmniRoute/pull/5201) — 感谢 @KooshaPari) +- **fix(dashboard): 首页更新步骤警告图标使用 amber 色** — 首页更新步骤中的警告状态图标(`HomePageClient.tsx`)使用 `text-yellow-500`(Tailwind `#eab308`),在浅色背景上对比度很差(~1.9:1,低于 WCAG AA),且与同组件中所有同级元素使用的 `amber` 警告惯例不一致。切换为 `text-amber-500`——单行 `className` 变更,无行为变化。([#5176](https://github.com/diegosouzapw/OmniRoute/pull/5176)) -### 📝 Maintenance +### 📝 维护 -- **test(combo): deterministic context-relay universal-handoff coverage** — covers the universal (provider-agnostic) session-handoff path in `context-relay` (`combo.ts:2099–2139`), which previously had only a definition-order assertion and a `TODO(phase-2)`. The test drives the real pipeline via session seams (`x-session-id` → `relayOptions.sessionId` → `maybeGenerateUniversalHandoff`) without live infrastructure. ([#5168](https://github.com/diegosouzapw/OmniRoute/pull/5168)) -- **test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity)** — adds the missing E2E test for the `quota-share` strategy, driving the real `handleChat` → chatCore → `selectQuotaShareTarget` → executor pipeline via in-process seams and asserting which connection is dispatched. The DRR selector already had 29 unit tests; this closes the E2E gap and brings quota-share to parity with the 17-strategy public matrix. ([#5179](https://github.com/diegosouzapw/OmniRoute/pull/5179)) -- **test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap)** — covers the codex-specific handoff block of `context-relay` (`combo.ts:2143–2183`), which #5168 left documented-but-untested because it requires a `codex` connection. All seams (`fetchCodexQuota`, handoff generation, session relay) are mocked deterministically without live infra. ([#5195](https://github.com/diegosouzapw/OmniRoute/pull/5195)) -- **test(ci): wire antigravity-quota-family test under `test:vitest` (fix test-discovery orphan)** — `open-sse/services/__tests__/antigravity-quota-family.test.ts` (introduced by #5180) was not collected by any active runner, causing `check:test-discovery` to report a new orphan and gate every subsequent PR on the release branch. The file is now added to `vitest.mcp.config.ts` `include` and the corresponding orphan-allowlist entry is removed. ([#5196](https://github.com/diegosouzapw/OmniRoute/pull/5196)) -- **test(security): regression guard — PII redaction stays opt-in (default off) + Hard Rule #20** — adds a test asserting both `PII_REDACTION_ENABLED` and `PII_RESPONSE_SANITIZATION` feature-flag `defaultValue` fields are `"false"` and that data passes through all three application points (`piiMasker`, `piiSanitizer`, `streamingPiiTransform`) untouched when both flags are off, encoding Hard Rule #20 as a CI-enforced contract and fixing a misleading doc implication that PII masking was on by default. ([#5159](https://github.com/diegosouzapw/OmniRoute/pull/5159)) -- **docs(i18n): add Traditional Chinese (zh-TW) README + update zh-CN** — adds a new Traditional Chinese translation (`docs/i18n/zh-TW/README.md`) and updates the Simplified Chinese README to the current English baseline; the language index (`docs/i18n/README.md`) and root `README.md` badge row are updated accordingly. ([#5162](https://github.com/diegosouzapw/OmniRoute/pull/5162) — thanks @lunkerchen) -- **docs(i18n): full sync of zh-TW and zh-CN README to canonical English v3.8.39** — brings both translations to full parity, adding the complete What's New section, compression real-token examples, and all sections updated in the v3.8.38/39 English README. ([#5171](https://github.com/diegosouzapw/OmniRoute/pull/5171) — thanks @lunkerchen) -- **docs(combo): sync combo/routing-strategy docs to current state + document test coverage** — removes a stale ordinal from the Fusion bullet in `README.md`; adds a new **Testing & Coverage** section to `docs/routing/AUTO-COMBO.md` documenting the deterministic strategy matrix (`npm run test:combo:matrix`), quota-share DRR E2E coverage, and context-relay handoff tests delivered across the v3.8.39 cycle. ([#5185](https://github.com/diegosouzapw/OmniRoute/pull/5185)) -- **fix(docker):** copy the `open-sse` workspace manifest before `npm ci` so workspace-only deps install — the Dockerfile copied only the root `package*.json`, so `npm ci` skipped `safe-regex` and `@toon-format/toon` (declared in `open-sse/package.json`, not hoisted to root), breaking the multi-arch image build with `Module not found` during `npm run build`. (thanks @diegosouzapw) +- **test(combo): 确定性 context-relay 通用交接覆盖** — 覆盖 `context-relay` 中通用的(与服务商无关的)会话交接路径(`combo.ts:2099–2139`),该路径此前仅有定义顺序断言和一个 `TODO(phase-2)`。测试通过会话接缝(`x-session-id` → `relayOptions.sessionId` → `maybeGenerateUniversalHandoff`)驱动真实管线,无需实时基础设施。([#5168](https://github.com/diegosouzapw/OmniRoute/pull/5168)) +- **test(combo): 端到端 quota-share DRR 路由决策覆盖(矩阵对齐)** — 为 `quota-share` 策略添加缺失的 E2E 测试,通过进程内接缝驱动真实的 `handleChat` → chatCore → `selectQuotaShareTarget` → executor 管线,并断言分发的连接。DRR 选择器已有 29 个单元测试;此次补全了 E2E 缺口,使 quota-share 与 17 策略公开矩阵保持一致。([#5179](https://github.com/diegosouzapw/OmniRoute/pull/5179)) +- **test(combo): 确定性 context-relay codex 配额交接覆盖(补全最后缺口)** — 覆盖 `context-relay` 中 codex 特定的交接块(`combo.ts:2143–2183`),该块在 #5168 中有记录但未经测试,因为需要 `codex` 连接。所有接缝(`fetchCodexQuota`、交接生成、会话中继)均以确定性方式 mock,无需实时基础设施。([#5195](https://github.com/diegosouzapw/OmniRoute/pull/5195)) +- **test(ci): 将 antigravity-quota-family 测试纳入 `test:vitest`(修复测试发现孤儿)** — `open-sse/services/__tests__/antigravity-quota-family.test.ts`(由 #5180 引入)未被任何活跃运行器收集,导致 `check:test-discovery` 报告新的孤儿并阻塞发布分支上的每个后续 PR。该文件现已添加到 `vitest.mcp.config.ts` 的 `include` 中,对应的孤儿允许列表条目已移除。([#5196](https://github.com/diegosouzapw/OmniRoute/pull/5196)) +- **test(security): 回归守护 — PII 脱敏保持可选(默认关闭)+ 第 20 号硬规则** — 添加测试断言 `PII_REDACTION_ENABLED` 和 `PII_RESPONSE_SANITIZATION` 特性标志的 `defaultValue` 字段均为 `"false"`,且两个标志均关闭时数据在三个应用点(`piiMasker`、`piiSanitizer`、`streamingPiiTransform`)中均未修改地通过,将第 20 号硬规则编码为 CI 强制执行的契约,并修复了 PII 脱敏默认开启的误导性文档表述。([#5159](https://github.com/diegosouzapw/OmniRoute/pull/5159)) +- **docs(i18n): 添加繁体中文(zh-TW)README + 更新简体中文** — 新增繁体中文翻译(`docs/i18n/zh-TW/README.md`),并将简体中文 README 更新到当前英文基线;语言索引(`docs/i18n/README.md`)和根 `README.md` 徽章行相应更新。([#5162](https://github.com/diegosouzapw/OmniRoute/pull/5162) — 感谢 @lunkerchen) +- **docs(i18n): zh-TW 和 zh-CN README 与标准英文 v3.8.39 全面同步** — 使两个翻译版本达到完全对齐,添加了完整的新功能章节、压缩真实 Token 示例以及 v3.8.38/39 英文 README 中更新的所有章节。([#5171](https://github.com/diegosouzapw/OmniRoute/pull/5171) — 感谢 @lunkerchen) +- **docs(combo): 将 Combo/路由策略文档同步到当前状态 + 记录测试覆盖率** — 从 `README.md` 的 Fusion 条目中删除过时的序号;在 `docs/routing/AUTO-COMBO.md` 中新增**测试与覆盖率**章节,记录确定性策略矩阵(`npm run test:combo:matrix`)、quota-share DRR E2E 覆盖率和 v3.8.39 周期中交付的 context-relay 交接测试。([#5185](https://github.com/diegosouzapw/OmniRoute/pull/5185)) +- **fix(docker):** 在 `npm ci` 之前复制 `open-sse` 工作区清单以确保仅工作区的依赖能够安装 — Dockerfile 仅复制了根目录的 `package*.json`,因此 `npm ci` 跳过了 `safe-regex` 和 `@toon-format/toon`(声明在 `open-sse/package.json` 中,未提升到根目录),导致多架构镜像构建在 `npm run build` 期间因 `Module not found` 而失败。感谢 @diegosouzapw --- ## [3.8.38] — 2026-06-27 -### ✨ New Features +### ✨ 新功能 -- **feat(sidebar): colored menu icons** — sidebar menu icons now render with a per-item accent color: curated colors for known items (`SIDEBAR_ICON_ACCENTS`) plus a deterministic hash-based fallback (`getSidebarIconAccent`) so every item gets a stable, distinct color across sessions. ([#3812](https://github.com/diegosouzapw/OmniRoute/pull/3812) — thanks @rafacpti23) -- **feat(providers): add Factory (factory.ai) as a subscription gateway provider** — `factory` (Factory Droids' hosted gateway) is now a first-class routing provider on the OpenAI-compatible `https://api.factory.ai/v1` endpoint with Bearer apikey auth; the key is supplied from the Dashboard connection (not env). ([#5065](https://github.com/diegosouzapw/OmniRoute/pull/5065) — thanks @KooshaPari) -- **feat(providers): add Grok Build (xAI) provider with OAuth import-token flow** — `grok-cli` (alias `gc`) routes through Grok's CLI chat proxy; users paste their `~/.grok/auth.json` (or the JWT), with automatic `refresh_token` rotation. The public xAI client_id is embedded via `resolvePublicCred("grok_id")` (Hard Rule #11), never a literal. ([#5020](https://github.com/diegosouzapw/OmniRoute/pull/5020) — thanks @fulorgnas) -- **feat(dashboard): click-to-edit model alias in the provider page** — click an alias to edit it inline (Enter/blur saves, Escape cancels), instead of only being able to delete and re-add it. ([#5119](https://github.com/diegosouzapw/OmniRoute/pull/5119) — thanks @waguriagentic) -- **feat(providers): add ZenMux Free (session-cookie free-tier) provider** — `zenmux-free` (alias `zmf`) with a dedicated executor translating ZenMux's Anthropic-style SSE to OpenAI format; ships 12 free-tier models (DeepSeek V3.2, GLM 4.7 Flash Free, etc.). ([#5105](https://github.com/diegosouzapw/OmniRoute/pull/5105) — thanks @mrnasil) -- **feat(providers): allow local/private provider URLs by default (`Allow Local Provider URLs` flag)** — adding/validating an OpenAI-compatible provider on a loopback/LAN address (e.g. `http://127.0.0.1:3264/api`) was rejected by the SSRF guard with "Blocked private or local provider URL", even though OmniRoute is local-first. A new `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` feature flag (default **ON**, toggle in Settings → Feature Flags) now scopes the provider-validation guard to allow local/private hosts while still blocking cloud-metadata endpoints (169.254.169.254, metadata.google.internal). Disable it to restore strict public-only blocking. Webhook/remote-image SSRF defaults are unchanged. ([#5066](https://github.com/diegosouzapw/OmniRoute/issues/5066), thanks @daniij) -- **feat(blackbox):** refresh provider model catalog with latest models. (thanks @ptkelanatechsolutions) -- **kiro**: inline `` stream splitter — when `enabled` is present, `assistantResponseEvent` content is now split into separate `delta.content` / `delta.reasoning_content` SSE chunks (new `open-sse/executors/kiroThinking.ts` module wired into `KiroExecutor.transformEventStreamToSSE`). -- **feat(cursor):** parse Cursor Composer DeepSeek-style inline tool calls — Composer `cu/composer-2.5*` models embed tool invocations in their visible text using `<|tool▁calls▁begin|>…<|tool▁calls▁end|>` markers instead of structured protobuf frames; a new streaming parser (`composerToolCalls.ts`) intercepts these in both streaming and non-streaming paths, suppresses the markers from the client-visible content, and emits proper OpenAI `tool_calls` deltas so downstream clients handle them natively. (thanks @noestelar) -- **feat(proxy):** support auth-less `host:port` batch import and surface proxy-test failures. (thanks @dimaslanjaka) -- **feat(video): Alibaba DashScope video provider (`wan2.7-t2v`)** — adds the `alibaba` video provider (DashScope async task → poll → MP4) wired through the standard apikey credential path, so text-to-video requests can route to Alibaba's `wan2.7-t2v` model. (thanks @josevictorferreira) -- **feat(cc): per-connection "summarized thinking display" toggle for Claude-Code-compatible providers** — exposes a connection-level toggle that drives the existing Copilot summarized-thinking marker, so operators can opt a CC-compatible connection into summarized reasoning display from the UI (schema + request defaults + provider modals, with i18n). (thanks @rdself) -- **feat(compression): compression playground in the studio (Play + Compare tabs)** — `/dashboard/compression/studio` gains a synthetic playground: paste text → per-engine **lanes** (each deterministic engine run alone via `/api/compression/preview`) plus a **combined waterfall** ordered by `stackPriority`, and a free **A/B Compare** grid with on-demand, **USD-capped** fidelity verdicts (`/api/compression/compare` + `compare/verify`). The preview route now uses the real cl100k tokenizer, returns `engineBreakdown`, and accepts an ordered `pipeline[]`; new `compare` / `compare/verify` / `retrieve` routes; the live WS feed moved to `/dashboard/compression/live`. Management-only. ([#5080](https://github.com/diegosouzapw/OmniRoute/pull/5080)) -- **feat(dashboard): expose Fusion `judgeModel` + `fusionTuning` in the combo editor** — the Fusion strategy editor now surfaces the judge model (synthesizes the panel answers; defaults to the first panel model) plus the quorum-grace tuning fields (`minPanel`, `stragglerGraceMs`, `panelHardTimeoutMs`) that `open-sse/services/fusion.ts` already reads. Schema-validated + bounded; empty tuning is never persisted. ([#5074](https://github.com/diegosouzapw/OmniRoute/pull/5074)) -- **feat(compression): opt-in per-step fidelity gate for the stacked pipeline** — each compression step can now be guarded by a pure fidelity checker (4 invariants, fail-open) so a lossy engine that would degrade the prompt past a threshold is rejected and its lane skipped instead of silently shipping. Configurable via `fidelityGate` (advanced thresholds intentionally API-omitted), with a per-lane rejection breakdown surfaced in the studio playground toggle. ([#5143](https://github.com/diegosouzapw/OmniRoute/pull/5143)) -- **feat(compression): fuzzy near-duplicate dedup (session-dedup 2nd pass)** — the session-dedup engine gains a second fuzzy pass that collapses near-duplicate (not just byte-identical) segments, with a playground toggle to compare on/off. ([#5143](https://github.com/diegosouzapw/OmniRoute/pull/5143)) -- **feat(quota): opt-in Codex/Claude auto-ping keepalive** — an opt-in background keepalive can periodically ping Codex/Claude connections to keep their session/quota state warm, reducing cold-start failures on the first real request. ([#5102](https://github.com/diegosouzapw/OmniRoute/pull/5102)) -- **feat(ops): SRE playbooks + ops helper scripts** — salvaged from a closed stale PR; adds operator runbooks and ops helper scripts. ([#5138](https://github.com/diegosouzapw/OmniRoute/pull/5138) — thanks @KooshaPari / @diegosouzapw) -- **feat(mcp): web-session robustness — cookie dedup + browser-pool observability** — the MCP web-session path now de-duplicates cookies when (re)hydrating a session (avoiding conflicting duplicate `Cookie` headers) and exposes browser-pool observability (pool size / in-use / acquisition metrics) for the headless web providers. ([#5121](https://github.com/diegosouzapw/OmniRoute/pull/5121), builds on [#3368](https://github.com/diegosouzapw/OmniRoute/issues/3368)) -- **feat(compression): Ionizer engine — lossy JSON-array sampling reversible via CCR** — a new compression engine that down-samples large JSON arrays to a representative subset and records a Compact Change Representation (CCR) so the omitted rows can be reconstructed, trading exactness for a large token reduction on tabular/array-heavy payloads. ([#5148](https://github.com/diegosouzapw/OmniRoute/pull/5148)) +- **feat(sidebar): 彩色菜单图标** — 侧边栏菜单图标现在以每项主题色渲染:已知项使用精选颜色(`SIDEBAR_ICON_ACCENTS`),再加上基于哈希的确定性回退方案(`getSidebarIconAccent`),使每项在会话间获得稳定、独特的颜色。([#3812](https://github.com/diegosouzapw/OmniRoute/pull/3812) — 感谢 @rafacpti23) +- **feat(providers): 新增 Factory (factory.ai) 订阅网关服务商** — `factory`(Factory Droids 的托管网关)现已成为 OpenAI 兼容端点 `https://api.factory.ai/v1` 上的一级路由服务商,使用 Bearer apikey 认证;密钥从仪表盘连接中提供(而非环境变量)。([#5065](https://github.com/diegosouzapw/OmniRoute/pull/5065) — 感谢 @KooshaPari) +- **feat(providers): add Grok Build (xAI) provider with OAuth import-token flow** — `grok-cli` (alias `gc`) routes through Grok's CLI chat proxy; users paste their `~/.grok/auth.json` (or the JWT), with automatic `refresh_token` rotation. The public xAI client_id is embedded via `resolvePublicCred("grok_id")` (Hard Rule #11), never a literal. ([#5020](https://github.com/diegosouzapw/OmniRoute/pull/5020) — 感谢 @fulorgnas) +- **feat(dashboard): 服务商页面中的模型别名支持点击编辑** — 点击别名即可内联编辑(Enter/失焦保存,Escape 取消),而不只能删除再重新添加。([#5119](https://github.com/diegosouzapw/OmniRoute/pull/5119) — 感谢 @waguriagentic) +- **feat(providers): 新增 ZenMux Free(会话 Cookie 免费层)服务商** — `zenmux-free`(别名 `zmf`),配备专用执行器,将 ZenMux 的 Anthropic 风格 SSE 翻译为 OpenAI 格式;包含 12 个免费层模型(DeepSeek V3.2、GLM 4.7 Flash Free 等)。([#5105](https://github.com/diegosouzapw/OmniRoute/pull/5105) — 感谢 @mrnasil) +- **feat(providers): 默认允许本地/私有服务商 URL(`Allow Local Provider URLs` 标志)** — 在 loopback/LAN 地址(如 `http://127.0.0.1:3264/api`)上添加/校验 OpenAI 兼容服务商时,会被 SSRF 守卫以 "Blocked private or local provider URL" 拒绝,尽管 OmniRoute 是本机优先的。新增 `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` 特性标志(默认**开启**,在设置 → 特性标志中切换),限定服务商校验守卫的范围:允许本地/私有主机,同时仍然阻止云元数据端点(169.254.169.254、metadata.google.internal)。禁用它可恢复到严格的仅公网模式。Webhook/远端镜像 SSRF 默认值不变。([#5066](https://github.com/diegosouzapw/OmniRoute/issues/5066),感谢 @daniij) +- **feat(blackbox):** 刷新服务商模型目录,更新为最新模型。(感谢 @ptkelanatechsolutions) +- **kiro**: 内联 `` 流分割器 — 当 `enabled` 存在时,`assistantResponseEvent` 内容现在被分割为独立的 `delta.content` / `delta.reasoning_content` SSE 块(新增 `open-sse/executors/kiroThinking.ts` 模块,接入 `KiroExecutor.transformEventStreamToSSE`)。 +- **feat(cursor):** 解析 Cursor Composer DeepSeek 风格的内联工具调用 — Composer `cu/composer-2.5*` 模型使用 `<|tool▁calls▁begin|>…<|tool▁calls▁end|>` 标记而非结构化 protobuf 帧来嵌入工具调用;新的流式解析器(`composerToolCalls.ts`)在流式和非流式路径中拦截这些标记,从客户端可见内容中去除标记,并发出符合标准的 OpenAI `tool_calls` delta 供下游客户端原生处理。感谢 @noestelar +- **feat(proxy):** 支持无认证的 `host:port` 批量导入,并展示代理测试失败信息。感谢 @dimaslanjaka +- **feat(video): 阿里云 DashScope 视频服务商(`wan2.7-t2v`)** — 新增 `alibaba` 视频服务商(DashScope 异步任务 → 轮询 → MP4),通过标准 apikey 凭证路径接入,使文本转视频请求可路由到阿里的 `wan2.7-t2v` 模型。感谢 @josevictorferreira +- **feat(cc): Claude-Code 兼容服务商的每连接"摘要思考显示"开关** — 暴露连接级别的开关,驱动现有的 Copilot 摘要思考标记,使运维人员可从 UI 将 CC 兼容连接配置为摘要推理显示(Schema + 请求默认值 + 服务商模态框,含 i18n)。感谢 @rdself +- **feat(compression): 工作室中的压缩游乐场(Play + Compare 标签页)** — `/dashboard/compression/studio` 新增合成游乐场:粘贴文本 → 按引擎的**通道**(每个确定性引擎通过 `/api/compression/preview` 单独运行)以及按 `stackPriority` 排序的**组合瀑布流**,以及带有按需、**USD 上限**保真度评分的自由 **A/B Compare** 网格(`/api/compression/compare` + `compare/verify`)。预览路由现在使用真实的 cl100k tokenizer,返回 `engineBreakdown`,并接受有序的 `pipeline[]` 参数;新增 `compare` / `compare/verify` / `retrieve` 路由;实时 WS 订阅迁移至 `/dashboard/compression/live`。仅限管理权限。([#5080](https://github.com/diegosouzapw/OmniRoute/pull/5080)) +- **feat(dashboard): 在 Combo 编辑器中暴露 Fusion `judgeModel` + `fusionTuning`** — Fusion 策略编辑器现在展示裁判模型(综合评审团答案;默认为第一个评审团模型)以及 quorum-grace 调优字段(`minPanel`、`stragglerGraceMs`、`panelHardTimeoutMs`),这些字段 `open-sse/services/fusion.ts` 已经读取。Schema 验证 + 有界;空调优永不持久化。([#5074](https://github.com/diegosouzapw/OmniRoute/pull/5074)) +- **feat(compression): 级联管线的可选每步保真度门禁** — 每个压缩步骤现在可以由纯保真度检查器守护(4 个不变量,敞开后效),因此如果某个有损引擎会使提示降级超过阈值,则拒绝该步骤并跳过其通道,而不是静默输出。通过 `fidelityGate` 配置(高级阈值有意排除在 API 之外),并在工作室 Playground 开关中展示每个通道的拒绝明细。([#5143](https://github.com/diegosouzapw/OmniRoute/pull/5143)) +- **feat(compression): 模糊近似去重(session-dedup 第二遍)** — session-dedup 引擎增加第二遍模糊匹配,折叠近似重复(而非仅字节完全相同)的片段,并提供工作室开关对比开启/关闭效果。([#5143](https://github.com/diegosouzapw/OmniRoute/pull/5143)) +- **feat(quota): 可选的 Codex/Claude 自动 ping 保活** — 可选的后台保活功能可定期 ping Codex/Claude 连接以保持其会话/配额状态预热,减少首次真实请求的冷启动失败。([#5102](https://github.com/diegosouzapw/OmniRoute/pull/5102)) +- **feat(ops): SRE 运维手册 + 运维辅助脚本** — 从已关闭的过期 PR 中抢救而来;新增运维 Runbook 和运维辅助脚本。([#5138](https://github.com/diegosouzapw/OmniRoute/pull/5138) — 感谢 @KooshaPari / @diegosouzapw) +- **feat(mcp): Web 会话健壮性——Cookie 去重 + 浏览器池可观测性** — MCP Web 会话路径现在在(重新)加载会话时对 Cookie 进行去重(避免冲突的重复 `Cookie` 头),并为无头 Web 服务商暴露浏览器池可观测性(池大小 / 使用中 / 获取指标)。([#5121](https://github.com/diegosouzapw/OmniRoute/pull/5121),基于 [#3368](https://github.com/diegosouzapw/OmniRoute/issues/3368)) +- **feat(compression): Ionizer 引擎——有损 JSON 数组采样,可通过 CCR 恢复** — 新的压缩引擎,将大型 JSON 数组下采样为代表性子集,并记录紧凑变更表示(CCR),以便可以重建被省略的行,在表格/数组密集型载荷上以精确性换取大幅 Token 减少。([#5148](https://github.com/diegosouzapw/OmniRoute/pull/5148)) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **fix(proxy): make the SOCKS5 handshake timeout operator-tunable (`SOCKS_HANDSHAKE_TIMEOUT_MS`)** — under high concurrency against a single residential gateway host, the SOCKS5 connect handshake could exceed the hardcoded 10s even though the proxy was reachable, surfacing as a false `[Proxy Fast-Fail] Proxy unreachable` (the pool size is already tunable via `OMNIROUTE_PROXY_DISPATCHER_CONNECTIONS`). The handshake timeout now reads `SOCKS_HANDSHAKE_TIMEOUT_MS` (default unchanged at `10000`, capped at `120000`) so a concurrency-heavy deployment can raise it without a code change. Mitigation for #5109 (the full concurrency-100 collapse still needs the reporter's live load-test confirmation). ([#5109](https://github.com/diegosouzapw/OmniRoute/issues/5109)) -- **fix(api): resolve `GET /v1/models/{id}` case-insensitively** — clients that normalise the model id (e.g. OpenCode requesting `minimax/minimax-m3` for the canonical catalog entry `minimax/MiniMax-M3`) missed the single-model lookup, which is case-sensitive, and fell back to advertising `context_length: 0`. `findModelById` now prefers an exact-case match and falls back to a case-insensitive match, so the real entry (and its context window) is returned regardless of casing. ([#5082](https://github.com/diegosouzapw/OmniRoute/issues/5082)) -- **fix(services): embed WS proxy honours `LIVE_WS_HOST`; reject empty `messages` early** — two headless/Docker deployment fixes (#5110). The embed WebSocket proxy (`:20131`) only read `EMBED_WS_PROXY_HOST`, so behind a reverse proxy/tunnel it stayed bound to `127.0.0.1` even with `LIVE_WS_HOST=0.0.0.0` set and the Live dashboard showed "WebSocket disconnected"; it now falls back to `LIVE_WS_HOST` (default still loopback). Separately, a request with an explicitly empty `messages: []` array was forwarded upstream and bounced back as a confusing raw `400/502`; `handleChat` now rejects it up front with a clear `messages: at least one message is required` (Responses-API `input` requests are unaffected). ([#5110](https://github.com/diegosouzapw/OmniRoute/issues/5110)) -- **fix(proxy): repair one-click Deno & Cloudflare relay deployments** — the `/api/settings/proxy/test` endpoint only recognized the `vercel` relay type, so testing a deployed Deno or Cloudflare relay returned `proxy.type must be http, https, or socks5` and never reached the relay; it now routes all relay types through `isRelayType()`. On installs with `STORAGE_ENCRYPTION_KEY` the relay-auth token is read via `extractRelayAuth` (encrypted `relayAuthEnc` form), fixing the silent `401` that left `publicIp` null. The Cloudflare Worker upload now sends the script part as `application/javascript` (the API rejects `application/javascript+module`; ES-module semantics come from `main_module`), and the proxy-registry schema accepts the `deno`/`cloudflare` types + `deno-relay`/`cloudflare-relay` sources so editing a deployed relay no longer 400s. ([#5128](https://github.com/diegosouzapw/OmniRoute/issues/5128)) -- **fix(kiro): retire `claude-sonnet-4.5` from the Kiro catalog + pin the exact Kiro 400 error** — `claude-sonnet-4.5` left the Kiro free-tier lineup (current active models: Opus 4.8/4.7/4.6, Sonnet 4.6, Haiku 4.5), so it is removed from the Kiro registry entry and the free-model catalog. A regression test now pins Kiro's verbatim `[400] Invalid model. Please select a different model to continue.` to the `isModelUnavailableError` model-unavailable classification. A 400 on every model (including current ones) points to a server-side Kiro tier/region gate, not an OmniRoute catalog bug. ([#5140](https://github.com/diegosouzapw/OmniRoute/pull/5140), closes [#4484](https://github.com/diegosouzapw/OmniRoute/issues/4484)) -- **fix(dashboard): preserve every rendered field when loading/saving Resilience settings** — `ResilienceTab` renders `comboCooldownWait` and `quotaShareConcurrencyLimit`, but both the initial-load and save paths rewrote component state without those fields, so after a successful `/api/resilience` response the cards received `undefined` and the page fell back to the generic "failed to load" state. A shared `toResilienceResponse()` mapper now keeps all rendered fields, and `PATCH /api/resilience` returns `quotaShareConcurrencyLimit` to match GET and the UI contract. ([#5139](https://github.com/diegosouzapw/OmniRoute/pull/5139) — thanks @rdself) -- **fix(quota): hydrate the in-memory quota cache from snapshots + scope auto-combo candidates** — after a restart the quota cache was empty, so a known-exhausted connection looked healthy until re-queried; `isAccountQuotaExhausted` now lazily hydrates from persisted `quota_snapshots`. Auto-combo candidate expansion is also scoped to the connections each combo target actually allows, instead of pulling in every connection for the provider. ([#5015](https://github.com/diegosouzapw/OmniRoute/pull/5015) — thanks @JxnLexn) -- **fix(resilience): harden quota cutoff, Gemini audio MIME, and model-lockout cooldown** — stored quota hard-cutoff values are no longer coerced to `enabled=true` from arbitrary strings; Gemini audio input parts have their MIME type validated/normalized before forwarding; and model lockout now honours the configured `maxCooldownMs` ceiling. ([#5093](https://github.com/diegosouzapw/OmniRoute/pull/5093) — thanks @KooshaPari) -- **fix(streaming): harden long OpenAI-compatible SSE streams** — a late pipeline-wind-down error can no longer overwrite an already-recorded successful stream (`streamCompletionRecorded` guard), client disconnects finalize as `499 client_disconnected` instead of poisoning provider/account failure state, JSON bodies that are actually SSE (wrong `application/json` content-type) are sniffed and re-streamed, and reasoning fields (`reasoning`/`reasoning_content` + OpenRouter/Gemini encrypted `reasoning_details`) are preserved through the JSON-as-SSE fallback. ([#5124](https://github.com/diegosouzapw/OmniRoute/pull/5124) — thanks @rdself) -- **fix(usage): dedupe request-usage logging and debounce stats events** — `saveRequestUsage` now guards against duplicate inserts (natural key: timestamp + provider + model + connection + api-key + token counts), back-fills a missing `endpoint`, and only emits `usageRecorded` when a row was actually inserted; stats `update`/`pending` event bursts are collapsed into a single debounced notification to reduce churn. ([#4940](https://github.com/diegosouzapw/OmniRoute/pull/4940) — thanks @nguyenxvotanminh3) -- **fix(sse): convert the native Gemini request body to OpenAI format in the Antigravity MITM handler** — `contents` / `systemInstruction` / `generationConfig` / `thinkingConfig` are now translated to OpenAI chat-completions format before forwarding to `/v1/chat/completions`, so thinking-capable models (e.g. `ag/claude-opus-4-6-thinking`) no longer fail with provider-side 400 "invalid argument" errors. ([#4845](https://github.com/diegosouzapw/OmniRoute/pull/4845) — thanks @anuragg-saxenaa) -- **fix(db): translate the two pt-BR SQLite driver-fallback log lines to English** — `[DB] Pré-inicializando sql.js WASM…` and `[DB] Drivers síncronos indisponíveis…` were the only non-English server log strings, mixing languages in the logs. Now `[DB] Pre-initializing sql.js WASM (synchronous drivers unavailable)…` / `[DB] Synchronous drivers unavailable — falling back to sql.js (WASM)`, guarded by a test that scans the driver path for accented log strings. ([#5103](https://github.com/diegosouzapw/OmniRoute/issues/5103)) -- **fix(diagnostics): non-streaming Claude responses no longer false-502 as `empty_choices`** — the v3.8.37 malformed-200 detector (#4942) only understood OpenAI `choices` and Responses-API `output` shapes, so a `/v1/messages` response that stays in Claude shape (`{type:"message", content:[…]}`) fell through to `empty_choices` → 502 (cascading to "All models failed" in a combo). Most visibly, an extended-thinking turn whose buffered body is a single **empty thinking block with a valid `signature`** (Claude Code's non-streaming Bash classifier) 502'd on every call. `detectMalformedNonStream` now understands the Claude shape: text/tool_use blocks and thinking blocks carrying a signature count as valid output, while a genuinely empty `content:[]` is still flagged. ([#5108](https://github.com/diegosouzapw/OmniRoute/issues/5108), thanks @insoln) -- **fix(combo): empty-content 502 now fails over within the same request instead of exhausting the provider** — a leg that answers HTTP 200 with no usable completion is rewritten to `502 "Provider returned empty content"`, but the combo exhaustion classifier treated that synthetic 502 as a connection-level failure (`#1731v2`) and marked the whole provider/connection exhausted, skipping every remaining **same-provider** leg in that request. The connection is actually healthy (it just returned an empty body), so empty-content 502s are now classified as model-level transient failures: the request advances to the next leg and the rest of that provider's legs stay eligible. Genuine gateway 502s still trip connection exhaustion. ([#5085](https://github.com/diegosouzapw/OmniRoute/issues/5085), thanks @andrea-kingautomation) -- **fix(dashboard): surface the detailed credential-validation error instead of a bare "invalid" badge** — the inline "Check" in the Add-Connection modal discarded the `error` message returned by `/api/providers/validate` and showed only an `invalid` badge. For web providers (claude-web / chatgpt-web) the real cause is often an environment error the backend already reports (e.g. `TLS impersonation client failed to start: EACCES … mkdir tls-client-node/bin`), so users were left guessing. The modal now renders the full reason next to the badge. ([#5088](https://github.com/diegosouzapw/OmniRoute/issues/5088), thanks @tkhs101) -- **fix(executors): strip `client_metadata` from forwarded body for Cerebras and Mistral** — Cerebras returns 400 (`wrong_api_format`) and Mistral returns 422 (`extra_forbidden`) when the passthrough body carries `client_metadata` (an OpenAI Codex / Claude CLI field with no equivalent on these upstreams). The default executor now drops it for these two providers before sending downstream; other providers (notably `openai`/`codex`) keep it. (thanks @saurabh321gupta) -- **fix(codebuddy):** only send reasoning params when the client requests reasoning. (thanks @anki1kr) -- **fix(sse):** keep streaming for forceStream providers when a JSON client requests it. Providers marked `forceStream:true` reject `stream:false` upstream (HTTP 400); `resolveStreamFlag` now guards against this so stream-only providers keep streaming even when the client sends `Accept: application/json` or `stream:false`. (thanks @anki1kr) -- **fix(sse):** prevent non-JSON SSE lines and duplicate `[DONE]` from breaking clients. (thanks @qianze0628) -- **fix(sse):** dedupe case-variant Anthropic headers in the executor `buildHeaders` path — Node/undici's `fetch` merges `anthropic-version` and `Anthropic-Version` into a single `"v, v"` value that the Anthropic API rejects, so both case variants are now collapsed to one canonical lowercase header (same for `anthropic-beta`). (thanks @Delcado19) -- **oauth(kiro):** support Kiro IDC (organization) token import — when the `~/.aws/sso/cache` token carries a `clientIdHash`, auto-import now reads the linked client registration file to obtain `clientId`/`clientSecret`, probes the Kiro IDE `profile.json` for `profileArn` (ARN region normalized to `us-east-1` for the runtime gateway), and refreshes via the regional AWS OIDC endpoint instead of the social path; the import schema and modal forward these credentials so manual imports also work for IDC tokens. (thanks @enjoyer-hub) -- **fix(translator):** preserve client `cache_control` breakpoints when routing Claude-format requests (e.g. Claude Code) to Alibaba DashScope's OpenAI-compatible providers (`alibaba` / `alibaba-cn`). The Claude→OpenAI translation previously stripped the markers from the system and message text blocks, so DashScope's explicit caching never engaged and every request was a cache miss. Cache hints now survive when preservation is requested for caching-capable OpenAI-format providers. (thanks @sacrtap) -- **fix(tts):** resolve Gemini TTS models from catalog and add `gemini-3.1-flash-tts-preview` as the new default Vertex TTS model. (thanks @nguyenha935) -- **fix(sse): don't cool down a healthy connection on a self-inflicted upstream timeout (504)** — when OmniRoute's own deadline elapses (surfaced as `TimeoutError`/`BodyTimeoutError` → 504), the connection is no longer disabled/failed-over, so a slow-but-healthy provider isn't penalised for our timeout. Genuine upstream 5xx/429 still trigger cooldown; antigravity keeps its own policy. (thanks @costaeder) -- **fix(translator):** forward image `tool_result` blocks as `image_url` instead of stringifying base64. (thanks @alican532) -- **fix(sse): robust Anthropic `/v1/messages` streaming — real ping keepalive + client-disconnect guard** — slow first tokens on reasoning models could trip strict clients' idle-read watchdog; the route now keeps the stream warm with a real `event: ping` (Anthropic clients ignore SSE comments) from the very first frame, and a client disconnect (AbortError / controller-closed) no longer counts as a provider failure (no failover/cooldown). (thanks @costaeder) -- **fix: preserve model hidden flags (`isHidden`) across model sync** — `replaceCustomModels` pruned the compat-override list to the new custom-model ids, silently wiping the `isHidden` flag of eye-hidden SYNCED models on every periodic sync / import (all hidden models turned back on). The redundant cleanup is removed (per-model removal already handles its own compat cleanup), so eye-hidden models stay hidden across re-sync. ([#5086](https://github.com/diegosouzapw/OmniRoute/pull/5086) — thanks @herjarsa) -- **fix(models): derive model-discovery config from the registry `modelsUrl`** — providers absent from the hardcoded `PROVIDER_MODELS_CONFIG` but carrying a registry `modelsUrl` (e.g. MiniMax) now get an auto-derived Bearer `/v1/models` discovery config, so "discover models" works instead of returning nothing. (thanks @herjarsa) -- **fix(compression): resolve worker + rule/filter assets via runtime anchors (standalone bundle)** — the LLMLingua worker and the RTK rule/filter loaders relied on `fileURLToPath(import.meta.url)`, which the standalone bundle freezes to the build-machine path, so the worker never spawned and rule/filter packs failed to resolve. They now anchor on `process.cwd()`/`argv[1]` (with `pathToFileURL` for the worker URL). (thanks @fulorgnas) -- **fix(api): sanitize error responses on seven management routes (Rule #12 hardening)** — `cli-tools/backups`, `cli-tools/guide-settings/[toolId]`, `logs/export`, `models/catalog`, `providers/test-batch`, `settings/import-json` and `usage/proxy-logs` no longer return raw `error.message`; they wrap caught errors in `sanitizeErrorMessage(...)`, and the routes are removed from the `check-error-helper` allowlist. (thanks @JxnLexn) -- **fix(sse): keep `output_text`-only Responses bodies from being dropped/false-502'd** — some upstreams return a shorthand Responses body whose answer is only in `output_text` with an empty `output[]`. `sanitizeResponsesApiResponse` discarded the text, so the response then tripped the malformed-200 guard. The sanitizer now synthesizes an `output[]` message item from a non-empty `output_text` (complements the Claude-native fix in #5108; both stem from #4942). -- **fix(executors): preserve a lone caller-supplied `Anthropic-Version` header casing** — the case-variant dedupe (#4846) unconditionally rewrote `Anthropic-Version`/`Anthropic-Beta` to lowercase even when only one variant was present, clobbering the caller's header. Dedupe now runs only when both case variants coexist (the actual undici-merge collision it was meant to fix). -- **fix(responses):** default `text.format` to `{ type: "text" }` for openai-compatible **responses** providers — some Responses-compatible upstreams (e.g. LM Studio) reject a `text` object missing `text.format` with a 400 `missing_required_parameter`; the default executor now fills the Responses-API default before forwarding (guarded to `openai-compatible-*responses*`, never overwriting an existing format). (thanks @StevanusPangau) -- **fix(translator): stop stripping client-provided `reasoning_content` for reasoning-replay providers** — the #4849 agentic-context strip (which drops `reasoning_content` from tool-call assistant turns to avoid O(n²) token growth) ran unconditionally, so replay providers (DeepSeek V4, Kimi K2, Qwen-Thinking, etc.) lost the client's reasoning and the reasoning-replay cache then overwrote it with a stale cached value (and such upstreams 400 without the original reasoning). The strip now skips reasoning-replay targets while non-reasoning providers keep the O(n²) protection. ([#5122](https://github.com/diegosouzapw/OmniRoute/pull/5122)) -- **fix(providers): add MiniMax M3 & Nemotron 3 Ultra to the Cline catalog** — the two models were missing from Cline's provider catalog and could not be selected; both are now registered. ([#5136](https://github.com/diegosouzapw/OmniRoute/pull/5136), closes [#3321](https://github.com/diegosouzapw/OmniRoute/issues/3321)) -- **fix(dashboard): key model-visibility toggle on the canonical `providerId`** — the per-model visibility toggle keyed off a display id, so toggling a model on one provider alias could mis-target another; it now keys on the canonical `providerId`. ([#5091](https://github.com/diegosouzapw/OmniRoute/pull/5091) — thanks @Theadd) -- **fix(diagnostics): recognize the Claude API format in `detectMalformedNonStream`** — salvaged null-guard so a Claude-shaped non-streaming body is no longer misclassified. ([#5141](https://github.com/diegosouzapw/OmniRoute/pull/5141) — thanks @herjarsa / @diegosouzapw) -- **fix(logging): track the final connection IDs in failover logs** — failover log lines now record the connection that actually served (or last failed) the request, instead of only the first attempt. ([#5016](https://github.com/diegosouzapw/OmniRoute/pull/5016) — thanks @JxnLexn) -- **fix(sse): ignore disconnect races during in-band stream error handling** — a client disconnect that races with in-band upstream error handling no longer surfaces as a spurious provider failure. ([#5007](https://github.com/diegosouzapw/OmniRoute/pull/5007) — thanks @JxnLexn) -- **fix(dashboard): surface the server error on `handleToggleCombo` failure** — a failed combo toggle now shows the backend error instead of silently no-op'ing. ([#5138](https://github.com/diegosouzapw/OmniRoute/pull/5138) — thanks @KooshaPari / @diegosouzapw) -- **fix(quota): track provider quota reset windows + enrich the Codex playground** — observed quota reset windows are tracked and surfaced, and the Codex playground gains the enriched quota metadata. ([#5141](https://github.com/diegosouzapw/OmniRoute/pull/5141) — thanks @Witroch4 / @diegosouzapw) -- **fix(sidebar): drop the orphan `settings` accent color** — removed a dangling accent-color entry that broke `typecheck:core`. ([#5142](https://github.com/diegosouzapw/OmniRoute/pull/5142)) -- **fix(sse): preserve non-stream reasoning fields for compatible clients** — non-streaming responses now keep the upstream reasoning fields (`reasoning` / `reasoning_content` and OpenRouter/Gemini `reasoning_details`) instead of stripping them in `responseSanitizer`, so clients that render reasoning on buffered responses no longer lose it. ([#5155](https://github.com/diegosouzapw/OmniRoute/pull/5155) — thanks @rdself) -- **fix(i18n): add missing English UI labels** — fills in untranslated English strings that were surfacing as raw keys in the dashboard. ([#5153](https://github.com/diegosouzapw/OmniRoute/pull/5153) — thanks @rdself) +- **fix(proxy): 使 SOCKS5 握手超时可运维调整(`SOCKS_HANDSHAKE_TIMEOUT_MS`)** — 在针对同一住宅网关主机的高并发下,SOCKS5 连接握手可能超过硬编码的 10 秒,即使代理可达,也会表现为虚假的 `[Proxy Fast-Fail] Proxy unreachable`(池大小已可通过 `OMNIROUTE_PROXY_DISPATCHER_CONNECTIONS` 调整)。握手超时现在读取 `SOCKS_HANDSHAKE_TIMEOUT_MS`(默认值保持 `10000`,上限 `120000`),使高并发部署可以无需代码更改提高此值。对 #5109 的缓解措施(完整的 concurrency-100 崩溃仍需报告者的现场压力测试确认)。([#5109](https://github.com/diegosouzapw/OmniRoute/issues/5109)) +- **fix(api): 不区分大小写地解析 `GET /v1/models/{id}`** — 规范化模型 id 的客户端(如 OpenCode 请求 `minimax/minimax-m3` 查找标准目录条目 `minimax/MiniMax-M3`)会错过区分大小写的单模型查询,回退到显示 `context_length: 0`。`findModelById` 现在优先精确大小写匹配,回退到不区分大小写匹配,因此无论大小写如何都返回真实条目(及其上下文窗口)。([#5082](https://github.com/diegosouzapw/OmniRoute/issues/5082)) +- **fix(services): 内嵌 WS 代理遵循 `LIVE_WS_HOST`;早期拒绝空 `messages`** — 两个无头/Docker 部署修复(#5110)。内嵌 WebSocket 代理(`:20131`)仅读取 `EMBED_WS_PROXY_HOST`,因此在反向代理/隧道后即使设置了 `LIVE_WS_HOST=0.0.0.0` 仍然绑定在 `127.0.0.1`,实时仪表盘显示"WebSocket disconnected";现在回退到 `LIVE_WS_HOST`(默认仍为 loopback)。此外,显式空 `messages: []` 数组的请求此前被转发到上游并以混乱的原始 `400/502` 返回;`handleChat` 现在以明确的 `messages: at least one message is required` 预先拒绝(Responses-API `input` 请求不受影响)。([#5110](https://github.com/diegosouzapw/OmniRoute/issues/5110)) +- **fix(proxy): 修复一键 Deno 和 Cloudflare 中继部署** — `/api/settings/proxy/test` 端点仅识别 `vercel` 中继类型,因此测试已部署的 Deno 或 Cloudflare 中继会返回 `proxy.type must be http, https, or socks5` 而从未到达中继;现在通过 `isRelayType()` 路由所有中继类型。在有 `STORAGE_ENCRYPTION_KEY` 的安装中,中继认证 Token 通过 `extractRelayAuth`(加密的 `relayAuthEnc` 形式)读取,修复了导致 `publicIp` 为 null 的静默 `401`。Cloudflare Worker 上传现在将脚本部分作为 `application/javascript` 发送(API 拒绝 `application/javascript+module`;ES 模块语义来自 `main_module`),且代理注册表 Schema 接受 `deno`/`cloudflare` 类型和 `deno-relay`/`cloudflare-relay` 来源,使编辑已部署的中继不再返回 400。([#5128](https://github.com/diegosouzapw/OmniRoute/issues/5128)) +- **fix(kiro): 从 Kiro 目录中移除 `claude-sonnet-4.5` + 精确匹配 Kiro 400 错误** — `claude-sonnet-4.5` 已离开 Kiro 免费层阵容(当前活跃模型:Opus 4.8/4.7/4.6、Sonnet 4.6、Haiku 4.5),因此从 Kiro 注册表条目和免费模型目录中移除。回归测试现在将 Kiro 的字面 `[400] Invalid model. Please select a different model to continue.` 精确匹配到 `isModelUnavailableError` 模型不可用分类。所有模型(包括当前模型)都返回 400 则指向服务端 Kiro 层级/区域门禁,而非 OmniRoute 目录错误。([#5140](https://github.com/diegosouzapw/OmniRoute/pull/5140),关闭 [#4484](https://github.com/diegosouzapw/OmniRoute/issues/4484)) +- **fix(dashboard): preserve every rendered field when loading/saving Resilience settings** — `ResilienceTab` renders `comboCooldownWait` and `quotaShareConcurrencyLimit`, but both the initial-load and save paths rewrote component state without those fields, so after a successful `/api/resilience` response the cards received `undefined` and the page fell back to the generic "failed to load" state. A shared `toResilienceResponse()` mapper now keeps all rendered fields, and `PATCH /api/resilience` returns `quotaShareConcurrencyLimit` to match GET and the UI contract. ([#5139](https://github.com/diegosouzapw/OmniRoute/pull/5139) — 感谢 @rdself) +- **fix(quota): 从快照中加载内存配额缓存 + 限定 Auto-Combo 候选范围** — 重启后配额缓存为空,因此已知已耗尽的连接在重新查询前看起来是健康的;`isAccountQuotaExhausted` 现在从持久化的 `quota_snapshots` 中延迟加载。Auto-Combo 候选扩展也限定为每个 Combo 目标实际允许的连接,而不是拉入该服务商的所有连接。([#5015](https://github.com/diegosouzapw/OmniRoute/pull/5015) — 感谢 @JxnLexn) +- **fix(resilience): 加固配额截断、Gemini 音频 MIME 和模型锁定冷却** — 存储的配额硬截断值不再从任意字符串强制转换为 `enabled=true`;Gemini 音频输入部分在转发前对其 MIME 类型进行校验/规范化;模型锁定现在遵循配置的 `maxCooldownMs` 上限。([#5093](https://github.com/diegosouzapw/OmniRoute/pull/5093) — 感谢 @KooshaPari) +- **fix(streaming): 加固长时间的 OpenAI 兼容 SSE 流** — 管线末尾阶段结束的错误不再覆盖已记录的流成功状态(`streamCompletionRecorded` 守卫),客户端断开连接最终化为 `499 client_disconnected` 而非污染服务商/账户失败状态,实际上是 SSE 的 JSON 体(错误的 `application/json` content-type)被嗅探并重新流式传输,推理字段(`reasoning`/`reasoning_content` + OpenRouter/Gemini 加密的 `reasoning_details`)通过 JSON-as-SSE 回退得以保留。([#5124](https://github.com/diegosouzapw/OmniRoute/pull/5124) — 感谢 @rdself) +- **fix(usage): 对请求用量日志去重并防抖统计事件** — `saveRequestUsage` 现在防止重复插入(自然键:timestamp + provider + model + connection + api-key + token 计数),补全缺失的 `endpoint`,并仅在实际插入行时才发出 `usageRecorded`;统计 `update`/`pending` 事件突发被折叠为单次防抖通知以减少抖动。([#4940](https://github.com/diegosouzapw/OmniRoute/pull/4940) — 感谢 @nguyenxvotanminh3) +- **fix(sse): 在 Antigravity MITM 处理器中将原生 Gemini 请求体转换为 OpenAI 格式** — `contents` / `systemInstruction` / `generationConfig` / `thinkingConfig` 现在在转发到 `/v1/chat/completions` 之前被翻译为 OpenAI chat-completions 格式,使支持思考的模型(如 `ag/claude-opus-4-6-thinking`)不再因服务商返回 400 "invalid argument" 错误而失败。([#4845](https://github.com/diegosouzapw/OmniRoute/pull/4845) — 感谢 @anuragg-saxenaa) +- **fix(db): 将两条 pt-BR SQLite 驱动回退日志行翻译为英文** — `[DB] Pré-inicializando sql.js WASM…` 和 `[DB] Drivers síncronos indisponíveis…` 是唯一的非英文服务器日志字符串,导致日志中语言混用。现在改为 `[DB] Pre-initializing sql.js WASM (synchronous drivers unavailable)…` / `[DB] Synchronous drivers unavailable — falling back to sql.js (WASM)`,并通过扫描驱动路径中带重音符号的日志字符串的测试来守护。([#5103](https://github.com/diegosouzapw/OmniRoute/issues/5103)) +- **fix(diagnostics): 非流式 Claude 响应不再因 `empty_choices` 而被误判为 502** — v3.8.37 的畸形 200 检测器(#4942)仅理解 OpenAI `choices` 和 Responses-API `output` 形状,因此保持 Claude 形状(`{type:"message", content:[…]}`)的 `/v1/messages` 响应落入 `empty_choices` → 502(在 Combo 中级联为 "All models failed")。最明显的是,扩展思考轮次中缓冲体为单个**携带有效 `signature` 的空思考块**(Claude Code 的非流式 Bash 分类器)在每次调用时都返回 502。`detectMalformedNonStream` 现在理解 Claude 形状:text/tool_use 块和携带签名的思考块计为有效输出,而真正空的 `content:[]` 仍被标记。([#5108](https://github.com/diegosouzapw/OmniRoute/issues/5108),感谢 @insoln) +- **fix(combo): 空内容 502 现在在同一请求内容灾,而不是耗尽服务商** — 返回 HTTP 200 但无可用于完成正文的分支被重写为 `502 "Provider returned empty content"`,但 Combo 耗尽分类器将该合成 502 视为连接级故障(`#1731v2`)并将整个服务商/连接标记为已耗尽,跳过该请求中所有剩余的**同服务商**分支。连接实际上是健康的(只是返回了空内容),因此空内容 502 现在被分类为模型级瞬态故障:请求推进到下一个分支,该服务商其余分支仍保持可选。真正的网关 502 仍然触发连接耗尽。([#5085](https://github.com/diegosouzapw/OmniRoute/issues/5085),感谢 @andrea-kingautomation) +- **fix(dashboard): 展示详细的凭证校验错误,而非仅仅显示"invalid"徽章** — 添加连接模态框中的内联"检查"丢弃了 `/api/providers/validate` 返回的 `error` 消息,只显示一个 `invalid` 徽章。对于 Web 服务商(claude-web / chatgpt-web),真正的原因往往是后端已报告的环境错误(如 `TLS impersonation client failed to start: EACCES … mkdir tls-client-node/bin`),因此用户只能猜测。模态框现在在徽章旁边渲染完整的原因文本。([#5088](https://github.com/diegosouzapw/OmniRoute/issues/5088),感谢 @tkhs101) +- **fix(executors): 从转发给 Cerebras 和 Mistral 的请求体中剥离 `client_metadata`** — 当透传请求体包含 `client_metadata`(OpenAI Codex / Claude CLI 字段,这些上游无对应项)时,Cerebras 返回 400(`wrong_api_format`),Mistral 返回 422(`extra_forbidden`)。默认执行器现在在向下游发送前为这两个服务商移除该字段;其他服务商(特别是 `openai`/`codex`)保留它。(感谢 @saurabh321gupta) +- **fix(codebuddy):** 仅在客户端请求推理时才发送推理参数。(感谢 @anki1kr) +- **fix(sse):** 当 JSON 客户端请求时,为强制流式传输服务商保持流式传输。被标记为 `forceStream:true` 的服务商拒绝 `stream:false` 上游(HTTP 400);`resolveStreamFlag` 现在对此进行防护,使仅支持流式的服务商即使在客户端发送 `Accept: application/json` 或 `stream:false` 时也保持流式传输。(感谢 @anki1kr) +- **fix(sse):** 防止非 JSON SSE 行和重复 `[DONE]` 破坏客户端。(感谢 @qianze0628) +- **fix(sse):** 在执行器 `buildHeaders` 路径中对大小写变体的 Anthropic 头去重 — Node/undici 的 `fetch` 将 `anthropic-version` 和 `Anthropic-Version` 合并为单个 `"v, v"` 值,Anthropic API 会拒绝该值,因此两种大小写变体现在折叠为一个规范的小写头(`anthropic-beta` 同理)。(感谢 @Delcado19) +- **oauth(kiro):** 支持 Kiro IDC(组织)Token 导入 — 当 `~/.aws/sso/cache` Token 携带 `clientIdHash` 时,自动导入现在读取链接的客户端注册文件以获取 `clientId`/`clientSecret`,探测 Kiro IDE `profile.json` 中的 `profileArn`(ARN 区域规范化为 `us-east-1` 用于运行时网关),并通过区域 AWS OIDC 端点而非社交路径进行刷新;导入 Schema 和模态框转发这些凭证,因此手动导入也适用于 IDC Token。感谢 @enjoyer-hub +- **fix(translator):** 当将 Claude 格式请求(如 Claude Code)路由到阿里云 DashScope 的 OpenAI 兼容服务商(`alibaba` / `alibaba-cn`)时,保留客户端 `cache_control` 断点。Claude→OpenAI 翻译之前从系统和消息文本块中剥离了这些标记,因此 DashScope 的显式缓存从未启用,每次请求都是缓存未命中。现在当为支持缓存能力的 OpenAI 格式服务商请求保留时,缓存提示得以保留。(感谢 @sacrtap) +- **fix(tts):** 从目录中解析 Gemini TTS 模型,并添加 `gemini-3.1-flash-tts-preview` 作为新的默认 Vertex TTS 模型。(感谢 @nguyenha935) +- **fix(sse): 不要在自身上游超时(504)导致健康连接进入冷却** — 当 OmniRoute 自身的截止时间到达时(表现为 `TimeoutError`/`BodyTimeoutError` → 504),连接不再被禁用/容灾切换,因此缓慢但健康的服务商不会因我们的超时而受惩罚。真正的上游 5xx/429 仍触发冷却;Antigravity 保持自己的策略。感谢 @costaeder +- **fix(translator):** 将图像 `tool_result` 块作为 `image_url` 转发,而不是将 base64 字符串化。(感谢 @alican532) +- **fix(sse): 稳健的 Anthropic `/v1/messages` 流式传输——真实 ping 保活 + 客户端断开守卫** — 推理模型缓慢的首个 Token 可能触发严格客户端的空闲读看门狗;该路由现在从第一帧开始以真实的 `event: ping`(Anthropic 客户端忽略 SSE 注释)保持流连接活跃,且客户端断开(AbortError / controller-closed)不再被计为服务商故障(无容灾/冷却)。感谢 @costaeder +- **fix: 在模型同步期间保留模型隐藏标志(`isHidden`)** — `replaceCustomModels` 将兼容覆盖列表修剪为新的自定义模型 ID,在每次定期同步/导入时静默擦除已隐藏的同步模型的 `isHidden` 标志(所有隐藏模型重新变为可见)。移除了冗余清理(每个模型的删除已经处理其自身的兼容清理),因此隐藏模型在重新同步期间保持隐藏。([#5086](https://github.com/diegosouzapw/OmniRoute/pull/5086) — 感谢 @herjarsa) +- **fix(models): 从注册表 `modelsUrl` 推导模型发现配置** — 不在硬编码 `PROVIDER_MODELS_CONFIG` 中但携带注册表 `modelsUrl` 的服务商(如 MiniMax),现在自动获得 Bearer `/v1/models` 发现配置,因此"发现模型"能正常工作而不是返回空。感谢 @herjarsa +- **fix(compression): 通过运行时锚点解析 Worker + 规则/过滤器资源文件(独立打包)** — LLMLingua Worker 和 RTK 规则/过滤器加载器依赖 `fileURLToPath(import.meta.url)`,而独立打包会将其冻结为构建机器路径,因此 Worker 永不启动、规则/过滤器包无法解析。现在基于 `process.cwd()`/`argv[1]` 进行锚定(Worker URL 使用 `pathToFileURL`)。感谢 @fulorgnas +- **fix(api): 对 7 个管理路由的错误响应进行脱敏处理(第 12 号硬规则加固)** — `cli-tools/backups`、`cli-tools/guide-settings/[toolId]`、`logs/export`、`models/catalog`、`providers/test-batch`、`settings/import-json` 和 `usage/proxy-logs` 不再返回原始 `error.message`;它们将捕获的错误包装在 `sanitizeErrorMessage(...)` 中,且这些路由已从 `check-error-helper` 白名单中移除。(感谢 @JxnLexn) +- **fix(sse): 防止仅 `output_text` 的 Responses 正文被丢弃/误判为 502** — 某些上游返回简写的 Responses 正文,其答案仅在 `output_text` 中,`output[]` 为空。`sanitizeResponsesApiResponse` 丢弃了文本,导致响应随后触发了畸形 200 守卫。脱敏器现在从非空 `output_text` 合成一个 `output[]` 消息项(补充 #5108 中的 Claude 原生修复;两者均源自 #4942)。 +- **fix(executors): 保留调用者单一大写的 `Anthropic-Version` 头** — 大小写变体去重(#4846)无条件地将 `Anthropic-Version`/`Anthropic-Beta` 重写为小写,即使只有一个变体存在,破坏了调用者的头部。去重现在仅在两种大小写变体同时存在时运行(该功能原本要修复的 undici-merge 冲突场景)。 +- **fix(responses):** 为 openai-compatible **responses** 服务商默认设置 `text.format` 为 `{ type: "text" }` — 某些 Responses 兼容上游(如 LM Studio)会以 400 `missing_required_parameter` 拒绝缺少 `text.format` 的 `text` 对象;默认执行器现在在转发前填充 Responses-API 默认值(限定于 `openai-compatible-*responses*`,永不覆盖已有格式)。感谢 @StevanusPangau +- **fix(translator): 停止对推理重放服务商剥离客户端提供的 `reasoning_content`** — #4849 的 agentic-context 剥离(从工具调用助手轮次中删除 `reasoning_content` 以避免 O(n²) Token 增长)无条件运行,因此重放服务商(DeepSeek V4、Kimi K2、Qwen-Thinking 等)丢失了客户端的推理,推理重放缓存随后用过期的缓存值覆盖它(此类上游在缺少原始推理时会返回 400)。剥离现在跳过推理重放目标,非推理服务商保持 O(n²) 保护。([#5122](https://github.com/diegosouzapw/OmniRoute/pull/5122)) +- **fix(providers): 将 MiniMax M3 和 Nemotron 3 Ultra 添加到 Cline 目录** — 这两个模型在 Cline 的服务商目录中缺失且无法选择;两者现在已注册。([#5136](https://github.com/diegosouzapw/OmniRoute/pull/5136),关闭 [#3321](https://github.com/diegosouzapw/OmniRoute/issues/3321)) +- **fix(dashboard): 基于标准 `providerId` 的模型可见性开关** — 每个模型的可见性开关此前基于显示 ID,因此在一个服务商别名上切换模型可能会误操作到另一个;现在基于标准 `providerId`。([#5091](https://github.com/diegosouzapw/OmniRoute/pull/5091) — 感谢 @Theadd) +- **fix(diagnostics): 在 `detectMalformedNonStream` 中识别 Claude API 格式** — 抢救性添加 null 守卫,使 Claude 形状的非流式正文不再被错误分类。([#5141](https://github.com/diegosouzapw/OmniRoute/pull/5141) — 感谢 @herjarsa / @diegosouzapw) +- **fix(logging): 在容灾日志中跟踪最终的连接 ID** — 容灾日志行现在记录实际服务(或最后失败)该请求的连接,而不仅仅是首次尝试。([#5016](https://github.com/diegosouzapw/OmniRoute/pull/5016) — 感谢 @JxnLexn) +- **fix(sse): 在带内流错误处理期间忽略断开竞争** — 与带内上游错误处理竞争的客户端断开连接,不再表现为虚假的服务商故障。([#5007](https://github.com/diegosouzapw/OmniRoute/pull/5007) — 感谢 @JxnLexn) +- **fix(dashboard): 在 `handleToggleCombo` 失败时展示服务端错误** — Combo 切换失败现在显示后端错误,而不是静默无操作。([#5138](https://github.com/diegosouzapw/OmniRoute/pull/5138) — 感谢 @KooshaPari / @diegosouzapw) +- **fix(quota): 跟踪服务商配额重置窗口 + 充实 Codex 工作室** — 观察到的配额重置窗口被跟踪并展示,Codex 工作室获得丰富的配额元数据。([#5141](https://github.com/diegosouzapw/OmniRoute/pull/5141) — 感谢 @Witroch4 / @diegosouzapw) +- **fix(sidebar): 删除孤立的 `settings` 主题色** — 移除一个导致 `typecheck:core` 失败的悬空主题色条目。([#5142](https://github.com/diegosouzapw/OmniRoute/pull/5142)) +- **fix(sse): 为兼容客户端保留非流式推理字段** — 非流式响应现在保留上游推理字段(`reasoning` / `reasoning_content` 和 OpenRouter/Gemini `reasoning_details`),而不是在 `responseSanitizer` 中剥离它们,使渲染缓冲响应推理的客户端不再丢失推理内容。([#5155](https://github.com/diegosouzapw/OmniRoute/pull/5155) — 感谢 @rdself) +- **fix(i18n): 添加缺失的英文 UI 标签** — 补全在仪表盘中以原始键名显示的未翻译英文字符串。([#5153](https://github.com/diegosouzapw/OmniRoute/pull/5153) — 感谢 @rdself) -### 🔒 Security +### 🔒 安全 -- **fix(security): exact-host Anthropic `baseUrl` check** — the Anthropic base-URL guard used a substring match that a crafted host could partially satisfy; it now requires an exact host match (resolves CodeQL `js/incomplete-url-substring-sanitization` alert #674). ([#5130](https://github.com/diegosouzapw/OmniRoute/pull/5130)) +- **fix(security): 精确主机名的 Anthropic `baseUrl` 检查** — Anthropic base-URL 守卫使用了子串匹配,精心构造的主机可以部分满足条件;现在要求精确主机匹配(解决 CodeQL `js/incomplete-url-substring-sanitization` 告警 #674)。([#5130](https://github.com/diegosouzapw/OmniRoute/pull/5130)) -### 📝 Maintenance +### 📝 维护 -- **refactor(store): remove dead legacy store modules** — salvaged cleanup of unused legacy store code. ([#5138](https://github.com/diegosouzapw/OmniRoute/pull/5138) — thanks @JxnLexn / @diegosouzapw) -- **test(combo): deterministic routing-decision matrix for all 17 strategies** — a deterministic E2E matrix pins the routing decision of every combo strategy. ([#5146](https://github.com/diegosouzapw/OmniRoute/pull/5146)) +- **refactor(store): 移除死代码中的旧版 store 模块** — 抢救性清理未使用的旧版 store 代码。([#5138](https://github.com/diegosouzapw/OmniRoute/pull/5138) — 感谢 @JxnLexn / @diegosouzapw) +- **test(combo): 全部 17 策略的确定性路由决策矩阵** — 确定性 E2E 矩阵固定了每个 Combo 策略的路由决策。([#5146](https://github.com/diegosouzapw/OmniRoute/pull/5146)) - **chore:** baseline reconciliations (complexity / file-size / cognitive), golden-snapshot + apikey-count alignment for new providers, orphan-test relocation, release base-red repairs, CHANGELOG i18n mirror sync, and an `actions/cache` 5→6 bump. ([#5145](https://github.com/diegosouzapw/OmniRoute/pull/5145), [#5144](https://github.com/diegosouzapw/OmniRoute/pull/5144), [#5125](https://github.com/diegosouzapw/OmniRoute/pull/5125), [#5126](https://github.com/diegosouzapw/OmniRoute/pull/5126), [#5120](https://github.com/diegosouzapw/OmniRoute/pull/5120), [#5117](https://github.com/diegosouzapw/OmniRoute/pull/5117), [#5112](https://github.com/diegosouzapw/OmniRoute/pull/5112)) -- **test:** gated live smoke for combo strategies (in-process + VPS HTTP) and refreshed release expectations to match current code. ([#5151](https://github.com/diegosouzapw/OmniRoute/pull/5151), [#5150](https://github.com/diegosouzapw/OmniRoute/pull/5150) — thanks @KooshaPari / @diegosouzapw) +- **test:** 带门控的 Combo 策略实况冒烟测试(进程内 + VPS HTTP)并刷新发布期望以匹配当前代码。([#5151](https://github.com/diegosouzapw/OmniRoute/pull/5151), [#5150](https://github.com/diegosouzapw/OmniRoute/pull/5150) — 感谢 @KooshaPari / @diegosouzapw) --- ## [3.8.37] — 2026-06-26 -### ✨ New Features +### ✨ 新功能 -- **feat(providers):** add DGrid AI gateway provider — OpenAI-compatible gateway at `api.dgrid.ai/v1` (alias `dgrid`, API-key auth, passthrough models). Free router tier (10 RPM / 100 RPD); a $5 lifetime top-up raises limits to 20 RPM / 1,000 RPD. ([#4931](https://github.com/diegosouzapw/OmniRoute/pull/4931) — thanks @dgridOP) +- **feat(providers):** 新增 DGrid AI 网关服务商 — OpenAI 兼容网关,位于 `api.dgrid.ai/v1`(别名 `dgrid`,API-key 认证,透传模型)。免费路由层(10 RPM / 100 RPD);$5 终身充值将限制提升至 20 RPM / 1,000 RPD。([#4931](https://github.com/diegosouzapw/OmniRoute/pull/4931) — 感谢 @dgridOP) -- **feat(providers):** add Pioneer AI (Fastino Labs) provider — OpenAI-compatible chat completions at `api.pioneer.ai/v1`. Registered with alias `pn`, `X-API-Key` auth, and a catalog of 10 open-tier serverless models (Qwen3, Llama 3.1/3.2, Gemma 3, SmolLM3). Free $75 credits, no credit card required. Gated enterprise models (Claude/GPT/Gemini) require prior fine-tuning on the Pioneer platform and are intentionally excluded from the catalog. ([#4909](https://github.com/diegosouzapw/OmniRoute/pull/4909) — thanks @HikiNarou) +- **feat(providers):** 新增 Pioneer AI(Fastino Labs)服务商 — OpenAI 兼容的聊天补全,位于 `api.pioneer.ai/v1`。注册别名 `pn`,`X-API-Key` 认证,包含 10 个开放层无服务器模型目录(Qwen3、Llama 3.1/3.2、Gemma 3、SmolLM3)。免费 $75 积分,无需信用卡。企业模型(Claude/GPT/Gemini)需要先在 Pioneer 平台上进行微调,有意排除在目录之外。([#4909](https://github.com/diegosouzapw/OmniRoute/pull/4909) — 感谢 @HikiNarou) -- **feat(providers):** add xAI Grok inbound translators and a thinking patcher — Grok requests are now translated on the inbound path and reasoning is normalized so Grok modes behave consistently across clients. ([#4910](https://github.com/diegosouzapw/OmniRoute/pull/4910) — thanks @mugnimaestra) +- **feat(providers):** 新增 xAI Grok 入站翻译器和思考修补器 — Grok 请求现在在入站路径上被翻译,推理被规范化,使 Grok 模式在不同客户端间行为一致。([#4910](https://github.com/diegosouzapw/OmniRoute/pull/4910) — 感谢 @mugnimaestra) -- **feat(oauth):** Codex bulk-import endpoint — `POST /api/oauth/codex/import` accepts multiple Codex OAuth credentials in one call for fast multi-account onboarding. ([#4914](https://github.com/diegosouzapw/OmniRoute/pull/4914) — thanks @beaaan) +- **feat(oauth):** Codex 批量导入端点 — `POST /api/oauth/codex/import` 接受一次调用多个 Codex OAuth 凭证,实现快速多账户接入。([#4914](https://github.com/diegosouzapw/OmniRoute/pull/4914) — 感谢 @beaaan) -- **feat(embeddings):** add a `dimensions` override field to embedding combos so an embedding combo can pin the output vector size per target. ([#4913](https://github.com/diegosouzapw/OmniRoute/pull/4913) — thanks @wenzetan) +- **feat(embeddings):** 为嵌入 Combo 添加 `dimensions` 覆盖字段,使嵌入 Combo 可以按目标固定输出向量大小。([#4913](https://github.com/diegosouzapw/OmniRoute/pull/4913) — 感谢 @wenzetan) -- **feat(sse):** auto-promote successful combo model — a new opt-in `comboAutoPromoteEnabled` setting reorders a combo's persisted model list so that, when a combo model responds successfully, it is moved to position #1 for future requests. ([#4852](https://github.com/diegosouzapw/OmniRoute/pull/4852) — thanks @arssnndr) +- **feat(sse):** 成功 Combo 模型自动提升 — 新的可选 `comboAutoPromoteEnabled` 设置重新排序 Combo 持久化的模型列表,当 Combo 模型成功响应时,它会被移到未来请求的第 #1 位置。([#4852](https://github.com/diegosouzapw/OmniRoute/pull/4852) — 感谢 @arssnndr) -- **feat(sse):** add toggleable tool-source diagnostics — an opt-in switch surfaces where each tool definition originated when debugging tool-routing issues. ([#4856](https://github.com/diegosouzapw/OmniRoute/pull/4856) — thanks @DuyPrX) +- **feat(sse):** 添加可切换的工具来源诊断 — 可选开关在调试工具路由问题时显示每个工具定义的来源。([#4856](https://github.com/diegosouzapw/OmniRoute/pull/4856) — 感谢 @DuyPrX) -- **feat(headroom):** proxy lifecycle management + dashboard UI — start/stop/monitor a Headroom compression proxy from the dashboard, with Docker sidecar support. ([#4649](https://github.com/diegosouzapw/OmniRoute/pull/4649) — thanks @diegosouzapw / @carmelogunsroses) +- **feat(headroom):** 代理生命周期管理 + 仪表盘 UI — 从仪表盘启动/停止/监控 Headroom 压缩代理,支持 Docker sidecar。([#4649](https://github.com/diegosouzapw/OmniRoute/pull/4649) — 感谢 @diegosouzapw / @carmelogunsroses) -- **feat(sse):** `x-omniroute-strip-reasoning` request header to drop `reasoning_content` from upstream responses (opt-in, preserving reasoning-aware clients). ([#4678](https://github.com/diegosouzapw/OmniRoute/pull/4678) — thanks @anuragg-saxenaa / @diegosouzapw) +- **feat(sse):** `x-omniroute-strip-reasoning` 请求头用于忽略上游响应中的 `reasoning_content`(可选,保留推理感知客户端)。([#4678](https://github.com/diegosouzapw/OmniRoute/pull/4678) — 感谢 @anuragg-saxenaa / @diegosouzapw) -- **feat(cli):** multi-model support for the Factory Droid CLI integration. ([#4682](https://github.com/diegosouzapw/OmniRoute/pull/4682) — thanks @anuragg-saxenaa / @diegosouzapw) +- **feat(cli):** Factory Droid CLI 集成的多模型支持。([#4682](https://github.com/diegosouzapw/OmniRoute/pull/4682) — 感谢 @anuragg-saxenaa / @diegosouzapw) -- **feat(sse):** parse Gemini CLI 429 `retryDelay` from the structured `RetryInfo` payload so cooldowns honor the upstream-provided backoff. ([#4738](https://github.com/diegosouzapw/OmniRoute/pull/4738) — thanks @NoxzRCW) +- **feat(sse):** 从结构化 `RetryInfo` 载荷解析 Gemini CLI 429 `retryDelay`,使冷却遵循上游提供的退避时间。([#4738](https://github.com/diegosouzapw/OmniRoute/pull/4738) — 感谢 @NoxzRCW) -- **feat(sse):** add GPT-4 and GPT-4o mini to the GitHub Copilot provider catalog. ([#4798](https://github.com/diegosouzapw/OmniRoute/pull/4798), [#4797](https://github.com/diegosouzapw/OmniRoute/pull/4797) — thanks @decolua) +- **feat(sse):** 将 GPT-4 和 GPT-4o mini 添加到 GitHub Copilot 服务商目录。([#4798](https://github.com/diegosouzapw/OmniRoute/pull/4798), [#4797](https://github.com/diegosouzapw/OmniRoute/pull/4797) — 感谢 @decolua) -- **feat(api):** add the `MiniMax-M3` pricing row (canonical + lowercase alias) so the new MiniMax default model gets accurate per-request cost accounting instead of falling back to a zero/default rate. ([#4814](https://github.com/diegosouzapw/OmniRoute/pull/4814) — thanks @octo-patch) +- **feat(api):** 添加 `MiniMax-M3` 定价行(标准名称 + 小写别名),使新的 MiniMax 默认模型获得准确的每次请求成本核算,而非回退到零/默认费率。([#4814](https://github.com/diegosouzapw/OmniRoute/pull/4814) — 感谢 @octo-patch) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **fix(sse):** dense, deterministic `response.output` ordering in `response.completed` — items are now sorted by their actual `output_index` (via a recorded-as-emitted accumulator + stable sort) instead of being rebuilt from unordered state dicts; `normalizeOutputIndex` replaces fragile `parseInt` calls for robust index coercion; superseded tool calls (replaced at the same index mid-stream) are excluded from the final output array. ([#4906](https://github.com/diegosouzapw/OmniRoute/pull/4906) — thanks @Marco9113) +- **fix(sse):** `response.completed` 中稠密、确定性的 `response.output` 排序 — 项目现在按其实际 `output_index` 排序(通过记录已发出的累加器 + 稳定排序),而不是从无序状态字典重建;`normalizeOutputIndex` 替换了脆弱的 `parseInt` 调用以实现稳健的索引转换;流中同一索引被替换的工具调用被排除在最终输出数组之外。([#4906](https://github.com/diegosouzapw/OmniRoute/pull/4906) — 感谢 @Marco9113) -- **fix(sse):** normalize Codex custom/freeform tools (`apply_patch`, `type:"custom"` with no `parameters`) to a `{ input: string }` function schema instead of an empty schema — the empty schema made models invoke `apply_patch` with `{}`, breaking the Codex runtime which expects `{ input: string }`. Also maps `custom_tool_call` / `custom_tool_call_output` input items and streams `apply_patch` tool calls via `custom_tool_call_input.delta`/`.done` events. ([#4862](https://github.com/diegosouzapw/OmniRoute/pull/4862) — thanks @nstung463) +- **fix(sse):** 将 Codex 自定义/自由格式工具(`apply_patch`、无 `parameters` 的 `type:"custom"`)规范化为 `{ input: string }` 函数 Schema 而非空 Schema — 空 Schema 使模型使用 `{}` 调用 `apply_patch`,破坏了期望 `{ input: string }` 的 Codex 运行时。还将 `custom_tool_call` / `custom_tool_call_output` 输入项映射,并通过 `custom_tool_call_input.delta`/`.done` 事件流式传输 `apply_patch` 工具调用。([#4862](https://github.com/diegosouzapw/OmniRoute/pull/4862) — 感谢 @nstung463) -- **fix(sse):** preserve the `required` array when translating Draft 2020-12 antigravity tool schemas (e.g. from OpenCode), stripping unsupported JSON Schema meta keywords while keeping mandatory arguments required so the model no longer calls tools without them. ([#4843](https://github.com/diegosouzapw/OmniRoute/pull/4843) — thanks @anuragg-saxenaa) +- **fix(sse):** 在翻译 Draft 2020-12 Antigravity 工具 Schema(如来自 OpenCode)时保留 `required` 数组,剥离不受支持的 JSON Schema 元关键词,同时保留必需参数使模型不再在调用工具时不带必需参数。([#4843](https://github.com/diegosouzapw/OmniRoute/pull/4843) — 感谢 @anuragg-saxenaa) -- **fix(sse):** Kiro tool-schema sanitizer — strip unsupported JSON-Schema keywords (`anyOf`/`$ref`/`if`-`then`, etc.) and hash-truncate tool names >64 chars before dispatch, mapping the streamed tool-call name back for the client, so Kiro no longer rejects tool calls with `400 "Improperly formed request"`. ([#4847](https://github.com/diegosouzapw/OmniRoute/pull/4847) — thanks @smarthomeblack) +- **fix(sse):** Kiro 工具 Schema 脱敏器 — 在调度前剥离不受支持的 JSON Schema 关键词(`anyOf`/`$ref`/`if`-`then` 等),并将超 64 字符的工具名哈希截断,将流式工具调用名映射回客户端,使 Kiro 不再以 `400 "Improperly formed request"` 拒绝工具调用。([#4847](https://github.com/diegosouzapw/OmniRoute/pull/4847) — 感谢 @smarthomeblack) -- **fix(sse):** make the `anthropic-version` default-guard case-insensitive for `anthropic-compatible-*` providers, so a caller/operator-supplied `Anthropic-Version` (any casing) is no longer clobbered by a second lowercase `anthropic-version: 2023-06-01` header. ([#4823](https://github.com/diegosouzapw/OmniRoute/pull/4823) — thanks @zakirkun) +- **fix(sse):** 使 `anthropic-compatible-*` 服务商的 `anthropic-version` 默认守卫不���分大小写,使调用者/运维人员提供的 `Anthropic-Version`(任意大小写)不再被第二个小写的 `anthropic-version: 2023-06-01` 头覆盖。([#4823](https://github.com/diegosouzapw/OmniRoute/pull/4823) — 感谢 @zakirkun) -- **fix(db):** validate HuggingFace API tokens via the `whoami-v2` endpoint as a pure auth probe so fine-grained Inference-Provider tokens (valid even when model/task endpoints reject them) are no longer falsely marked invalid; only 401/403 means an invalid key, other non-OK statuses surface as transient upstream errors. ([#4819](https://github.com/diegosouzapw/OmniRoute/pull/4819) — thanks @Delcado19) +- **fix(db):** 通过 `whoami-v2` 端点作为纯认证探针校验 HuggingFace API Token,使细粒度 Inference-Provider Token(即使模型/任务端点拒绝它们也有效)不再被错误标记为无效;仅 401/403 表示无效密钥,其他非 OK 状态表现为瞬态上游错误。([#4819](https://github.com/diegosouzapw/OmniRoute/pull/4819) — 感谢 @Delcado19) -- **fix(sse):** reject the Anthropic-only `[1m]` context-1m suffix in `buildKiroPayload` before it reaches AWS Bedrock — Kiro is Bedrock-backed and cannot honor the beta, so a forwarded `kr/*[1m]` model id was malformed upstream; callers now get a clear error pointing them at a direct-Anthropic provider for 1M-context routing. ([#4816](https://github.com/diegosouzapw/OmniRoute/pull/4816) — thanks @Delcado19) +- **fix(sse):** 在 `buildKiroPayload` 中拒绝仅 Anthropic 的 `[1m]` context-1m 后缀,在其到达 AWS Bedrock 之前阻止 — Kiro 由 Bedrock 支持且无法使用该 beta 功能,因此转发的 `kr/*[1m]` 模型 ID 在上游格式错误;调用者现在获得明确的错误,引导其使用直接 Anthropic 服务商进行 1M 上下文路由。([#4816](https://github.com/diegosouzapw/OmniRoute/pull/4816) — 感谢 @Delcado19) -- **fix(dashboard):** align the Engine Combos editor engines with the API schema — the named-combos pipeline dropdown offered four engines (`headroom`, `session-dedup`, `ccr`, `llmlingua`) that `PUT /api/context/combos/[id]` rejects, so selecting one made the save return 400 while the UI swallowed the error. The dropdown is now sourced from a single canonical engine map shared with `stackedPipelineStepSchema` (parity guarded by a unit test), and the editor surfaces save errors plus empty-name/empty-pipeline validation instead of failing quietly. ([#5062](https://github.com/diegosouzapw/OmniRoute/pull/5062) — closes #4955) +- **fix(dashboard): 使引擎 Combo 编辑器引擎与 API Schema 对齐** — 命名 Combo 管线下拉菜单提供了四个引擎(`headroom`、`session-dedup`、`ccr`、`llmlingua`),而 `PUT /api/context/combos/[id]` 会拒绝它们,因此选择一个会导致保存返回 400,同时 UI 静默吞噬了错误。下拉菜单现在从与 `stackedPipelineStepSchema` 共享的单一标准引擎映射中获取数据(由单元测试守护一致性),编辑器展示保存错误及空名称/空管线校验,而不是静默失败。([#5062](https://github.com/diegosouzapw/OmniRoute/pull/5062) — 关闭 #4955) -- **fix(sse):** surface malformed HTTP-200 upstream responses instead of treating them as success, so combo fallback can trigger. ([#4942](https://github.com/diegosouzapw/OmniRoute/pull/4942) — thanks @haipham22) +- **fix(sse):** 将畸形的 HTTP-200 上游响应展示为错误而非视为成功,使 Combo 容灾可以触发。([#4942](https://github.com/diegosouzapw/OmniRoute/pull/4942) — 感谢 @haipham22) -- **fix(antigravity):** retry transient upstream failures rather than failing the request outright. ([#4941](https://github.com/diegosouzapw/OmniRoute/pull/4941) — thanks @Jordannst) +- **fix(antigravity):** 对瞬态上游故障进行重试,而非直接使请求失败。([#4941](https://github.com/diegosouzapw/OmniRoute/pull/4941) — 感谢 @Jordannst) -- **fix(sse):** exclude WS-bridge controller-closed errors from the provider circuit breaker so a client disconnect no longer trips the whole provider. ([#4870](https://github.com/diegosouzapw/OmniRoute/pull/4870) — closes #4602, thanks @huohua-dev) +- **fix(sse):** 将 WS 桥接控制器关闭错误排除在服务商熔断器之外,使客户端断开连接不再触发整个服务商。([#4870](https://github.com/diegosouzapw/OmniRoute/pull/4870) — 关闭 #4602,感谢 @huohua-dev) -- **fix(sse):** resolve custom combos by id and case-insensitive name. ([#4869](https://github.com/diegosouzapw/OmniRoute/pull/4869) — closes #4446, thanks @herjarsa) +- **fix(sse):** 通过 ID 和不区分大小写的名称解析自定义 Combo。([#4869](https://github.com/diegosouzapw/OmniRoute/pull/4869) — 关闭 #4446,感谢 @herjarsa) -- **fix(sse):** forward AI SDK image parts in the Responses translator. ([#4859](https://github.com/diegosouzapw/OmniRoute/pull/4859) — thanks @mugnimaestra) +- **fix(sse):** 在 Responses 翻译器中转发 AI SDK 图像部分。([#4859](https://github.com/diegosouzapw/OmniRoute/pull/4859) — 感谢 @mugnimaestra) -- **fix(sse):** emit valid concatenable Kiro `tool_calls.arguments` deltas. ([#4855](https://github.com/diegosouzapw/OmniRoute/pull/4855) — thanks @wahyuzero) +- **fix(sse):** 发出有效可拼接的 Kiro `tool_calls.arguments` delta。([#4855](https://github.com/diegosouzapw/OmniRoute/pull/4855) — 感谢 @wahyuzero) -- **fix(sse):** strip `temperature` for Claude models with extended thinking enabled (the upstream rejects it). ([#4853](https://github.com/diegosouzapw/OmniRoute/pull/4853) — thanks @noestelar) +- **fix(sse):** 对启用扩展思考的 Claude 模型剥离 `temperature`(上游会拒绝它)。([#4853](https://github.com/diegosouzapw/OmniRoute/pull/4853) — 感谢 @noestelar) -- **fix(sse):** unwrap the Qoder HTTP-200 SSE error envelope so combo fallback can trigger. ([#4850](https://github.com/diegosouzapw/OmniRoute/pull/4850) — thanks @vianlearns) +- **fix(sse):** 解开 Qoder HTTP-200 SSE 错误信封,使 Combo 容灾可以触发。([#4850](https://github.com/diegosouzapw/OmniRoute/pull/4850) — 感谢 @vianlearns) -- **fix(sse):** strip reasoning blobs from agentic context to prevent O(n²) token growth across multi-turn agent loops. ([#4849](https://github.com/diegosouzapw/OmniRoute/pull/4849) — thanks @GodrezJr2) +- **fix(sse):** 从 agentic 上下文中剥离推理块,防止跨多轮代理循环的 O(n²) Token 增长。([#4849](https://github.com/diegosouzapw/OmniRoute/pull/4849) — 感谢 @GodrezJr2) -- **fix(sse):** close the reasoning block before message content in the Responses stream so clients render reasoning and answer in the right order. ([#4848](https://github.com/diegosouzapw/OmniRoute/pull/4848) — thanks @kwanLeeFrmVi) +- **fix(sse):** 在 Responses 流中消息内容之前关闭推理块,使客户端按正确顺序渲染推理和回答。([#4848](https://github.com/diegosouzapw/OmniRoute/pull/4848) — 感谢 @kwanLeeFrmVi) -- **fix(config):** sync the full SiliconFlow model list into the registry. ([#4844](https://github.com/diegosouzapw/OmniRoute/pull/4844) — thanks @letanphuc) +- **fix(config):** 将完整的 SiliconFlow 模型列表同步到注册表。([#4844](https://github.com/diegosouzapw/OmniRoute/pull/4844) — 感谢 @letanphuc) -- **fix(sse):** strip Composer `<|final|>` sentinel markers that leaked after Composer reasoning. ([#4842](https://github.com/diegosouzapw/OmniRoute/pull/4842) — thanks @noestelar) +- **fix(sse):** 剥离 Composer `<|final|>` 哨兵标记,该标记在 Composer 推理后泄漏。([#4842](https://github.com/diegosouzapw/OmniRoute/pull/4842) — 感谢 @noestelar) -- **fix(build):** trace-include `sql.js`'s `sql-wasm.wasm` in the standalone bundle so SQLite-WASM works in the packaged build. ([#4839](https://github.com/diegosouzapw/OmniRoute/pull/4839) — thanks @Delcado19) +- **fix(build):** 在独立打包中追踪包含 `sql.js` 的 `sql-wasm.wasm`,使 SQLite-WASM 在打包构建中正常工作。([#4839](https://github.com/diegosouzapw/OmniRoute/pull/4839) — 感谢 @Delcado19) -- **fix(cli):** persist lazily-installed native runtime deps (`better-sqlite3`, `systray2`) to the shared runtime `package.json` with `--save-exact` instead of `--no-save`, so installing one no longer prunes the other as "extraneous" — fixing a "No SQLite driver available" failure after a `--tray` install. ([#4841](https://github.com/diegosouzapw/OmniRoute/pull/4841) — thanks @omartuhintvs) +- **fix(cli):** 将延迟安装的原生运行时依赖(`better-sqlite3`、`systray2`)以 `--save-exact` 而非 `--no-save` 持久化到共享运行时 `package.json`,因此安装一个不再将另一个修剪为"extraneous"——修复了 `--tray` 安装后的 "No SQLite driver available" 故障。([#4841](https://github.com/diegosouzapw/OmniRoute/pull/4841) — 感谢 @omartuhintvs) -- **fix(sse):** resolve bare model names to a connection's `defaultModel` before upstream calls. ([#4825](https://github.com/diegosouzapw/OmniRoute/pull/4825) — thanks @anuragg-saxenaa) +- **fix(sse):** 在上游调用前将裸模型名解析为连接的 `defaultModel`。([#4825](https://github.com/diegosouzapw/OmniRoute/pull/4825) — 感谢 @anuragg-saxenaa) -- **fix(api):** surface a Docker-localhost hint on provider-node validation connection errors. ([#4822](https://github.com/diegosouzapw/OmniRoute/pull/4822) — thanks @anuragg-saxenaa) +- **fix(api):** 在服务商节点校验连接错误时展示 Docker localhost 提示。([#4822](https://github.com/diegosouzapw/OmniRoute/pull/4822) — 感谢 @anuragg-saxenaa) -- **fix(sse):** strip Gemini built-in tools when `functionDeclarations` are present in the Antigravity envelope (the two are mutually exclusive upstream). ([#4821](https://github.com/diegosouzapw/OmniRoute/pull/4821) — thanks @vanszs) +- **fix(sse):** 当 Antigravity 信封中存在 `functionDeclarations` 时剥离 Gemini 内置工具(两者在上游互斥)。([#4821](https://github.com/diegosouzapw/OmniRoute/pull/4821) — 感谢 @vanszs) -- **fix(sse):** strip `X-Stainless-*` headers and normalize the SDK `User-Agent` for OpenAI-compatible endpoints. ([#4820](https://github.com/diegosouzapw/OmniRoute/pull/4820) — thanks @anuragg-saxenaa) +- **fix(sse):** 剥离 `X-Stainless-*` 头并规范化 OpenAI 兼容端点的 SDK `User-Agent`。([#4820](https://github.com/diegosouzapw/OmniRoute/pull/4820) — 感谢 @anuragg-saxenaa) -- **fix(oauth):** allow a per-connection refresh lead-time override via `providerSpecificData.refreshLeadMs`. ([#4818](https://github.com/diegosouzapw/OmniRoute/pull/4818) — thanks @anuragg-saxenaa) +- **fix(oauth):** 允许通过 `providerSpecificData.refreshLeadMs` 进行每个连接的刷新提前量覆盖。([#4818](https://github.com/diegosouzapw/OmniRoute/pull/4818) — 感谢 @anuragg-saxenaa) -- **fix(dashboard):** resolve passthrough model aliases by `providerId` in `ModelSelectModal`. ([#4815](https://github.com/diegosouzapw/OmniRoute/pull/4815) — thanks @anuragg-saxenaa) +- **fix(dashboard): 在 `ModelSelectModal` 中通过 `providerId` 解析透传模型别名。([#4815](https://github.com/diegosouzapw/OmniRoute/pull/4815) — 感谢 @anuragg-saxenaa) -- **fix(sse):** strip `enumDescriptions` from Antigravity tool schemas. ([#4813](https://github.com/diegosouzapw/OmniRoute/pull/4813), [#4740](https://github.com/diegosouzapw/OmniRoute/pull/4740) — thanks @anuragg-saxenaa) +- **fix(sse):** 从 Antigravity 工具 Schema 中剥离 `enumDescriptions`。([#4813](https://github.com/diegosouzapw/OmniRoute/pull/4813), [#4740](https://github.com/diegosouzapw/OmniRoute/pull/4740) — 感谢 @anuragg-saxenaa) -- **fix(dashboard):** keep the desktop sidebar visible via an explicit CSS class. ([#4812](https://github.com/diegosouzapw/OmniRoute/pull/4812) — thanks @Delcado19) +- **fix(dashboard): 通过显式 CSS 类保持桌面侧边栏可见。([#4812](https://github.com/diegosouzapw/OmniRoute/pull/4812) — 感谢 @Delcado19) -- **fix(sse):** filter nameless hosted tools when converting Responses API to Chat format. ([#4789](https://github.com/diegosouzapw/OmniRoute/pull/4789) — upstream, thanks Владимир Акимов) +- **fix(sse):** 在将 Responses API 转换为聊天格式时过滤无名托管工具。([#4789](https://github.com/diegosouzapw/OmniRoute/pull/4789) — 上游,感谢 Владимир Акимов) -- **fix(sse):** stream-writer mock `abort()` now returns a Promise (test-stability fix). ([#4788](https://github.com/diegosouzapw/OmniRoute/pull/4788) — thanks @decolua) +- **fix(sse):** 流写入器 mock 的 `abort()` 现在返回 Promise(测试稳定性修复)。([#4788](https://github.com/diegosouzapw/OmniRoute/pull/4788) — 感谢 @decolua) -- **fix(sse):** use the WorkOS auth-token shape for Cline. ([#4787](https://github.com/diegosouzapw/OmniRoute/pull/4787) — thanks @apeltekci) +- **fix(sse):** 为 Cline 使用 WorkOS 认证 Token 形状。([#4787](https://github.com/diegosouzapw/OmniRoute/pull/4787) — 感谢 @apeltekci) -- **fix(api):** fall back to the existing access token for any OAuth provider when a refresh fails. ([#4786](https://github.com/diegosouzapw/OmniRoute/pull/4786) — thanks @decolua) +- **fix(api):** 当刷新失败时,为任何 OAuth 服务商回退到现有访问 Token。([#4786](https://github.com/diegosouzapw/OmniRoute/pull/4786) — 感谢 @decolua) -- **fix(sse):** read Antigravity usage from the `response.usageMetadata` envelope. ([#4785](https://github.com/diegosouzapw/OmniRoute/pull/4785) — thanks @decolua) +- **fix(sse):** 从 `response.usageMetadata` 信封中读取 Antigravity 用量。([#4785](https://github.com/diegosouzapw/OmniRoute/pull/4785) — 感谢 @decolua) -- **fix(oauth):** verify Cursor installation on Linux before auto-import. ([#4770](https://github.com/diegosouzapw/OmniRoute/pull/4770) — upstream, thanks Ibrahim Ryan) +- **fix(oauth):** 在自动导入前验证 Linux 上的 Cursor 安装。([#4770](https://github.com/diegosouzapw/OmniRoute/pull/4770) — 上游,感谢 Ibrahim Ryan) -- **fix(cli):** fall back to the default data dir when `DATA_DIR` is not writable. ([#4767](https://github.com/diegosouzapw/OmniRoute/pull/4767) — upstream, thanks Thiên Toán) +- **fix(cli):** 当 `DATA_DIR` 不可写时回退到默认数据目录。([#4767](https://github.com/diegosouzapw/OmniRoute/pull/4767) — 上游,感谢 Thiên Toán) -- **fix(sse):** `json_schema` fallback for OpenAI-compatible providers that don't support structured outputs. ([#4766](https://github.com/diegosouzapw/OmniRoute/pull/4766) — thanks @mustafabozkaya) +- **fix(sse):** 为不支持结构化输出的 OpenAI 兼容服务商提供 `json_schema` 回退。([#4766](https://github.com/diegosouzapw/OmniRoute/pull/4766) — 感谢 @mustafabozkaya) -- **fix(cli):** verify launchd registration and skip self-SIGTERM in macOS autostart. ([#4765](https://github.com/diegosouzapw/OmniRoute/pull/4765) — thanks @ntdung6868) +- **fix(cli):** 在 macOS 自启动中验证 launchd 注册并跳过自身 SIGTERM。([#4765](https://github.com/diegosouzapw/OmniRoute/pull/4765) — 感谢 @ntdung6868) -- **fix(sse):** finalize the `tool_calls` `finish_reason` on early stream end in the OpenAI Responses translator. ([#4764](https://github.com/diegosouzapw/OmniRoute/pull/4764) — thanks @decolua) +- **fix(sse):** 在 OpenAI Responses 翻译器中,早期流结束时完成 `tool_calls` 的 `finish_reason`。([#4764](https://github.com/diegosouzapw/OmniRoute/pull/4764) — 感谢 @decolua) -- **fix(sse):** gate Kiro image attachments behind a Claude-capability check. ([#4763](https://github.com/diegosouzapw/OmniRoute/pull/4763) — thanks @decolua) +- **fix(sse):** 在 Claude 能力检查之后才允许 Kiro 图像附件。([#4763](https://github.com/diegosouzapw/OmniRoute/pull/4763) — 感谢 @decolua) -- **fix(sse):** track Ollama streaming usage from raw NDJSON chunks. ([#4754](https://github.com/diegosouzapw/OmniRoute/pull/4754) — thanks @fresent) +- **fix(sse):** 从原始 NDJSON 块跟踪 Ollama 流式用量。([#4754](https://github.com/diegosouzapw/OmniRoute/pull/4754) — 感谢 @fresent) -- **fix(sse):** include low-level cause details in `formatProviderError`. ([#4741](https://github.com/diegosouzapw/OmniRoute/pull/4741) — thanks @decolua) +- **fix(sse):** 在 `formatProviderError` 中包含低级原因详情。([#4741](https://github.com/diegosouzapw/OmniRoute/pull/4741) — 感谢 @decolua) -- **fix(executors):** `anthropic-compatible-*` gateways now get a `Bearer` token alongside `x-api-key`. ([#4729](https://github.com/diegosouzapw/OmniRoute/pull/4729) — thanks @hodtien) +- **fix(executors):** `anthropic-compatible-*` 网关现在在 `x-api-key` 之外同时获得 `Bearer` Token。([#4729](https://github.com/diegosouzapw/OmniRoute/pull/4729) — 感谢 @hodtien) -- **fix(translator):** strip the `x-anthropic-billing-header` in the claude-to-openai path. ([#4728](https://github.com/diegosouzapw/OmniRoute/pull/4728) — thanks @weimaozhen) +- **fix(translator):** 在 claude-to-openai 路径中剥离 `x-anthropic-billing-header`。([#4728](https://github.com/diegosouzapw/OmniRoute/pull/4728) — 感谢 @weimaozhen) -- **fix(translator):** preserve `reasoning_effort` for non-Copilot Responses clients. ([#4688](https://github.com/diegosouzapw/OmniRoute/pull/4688) — thanks @ryanngit / @diegosouzapw) +- **fix(translator):** 为非 Copilot Responses 客户端保留 `reasoning_effort`。([#4688](https://github.com/diegosouzapw/OmniRoute/pull/4688) — 感谢 @ryanngit / @diegosouzapw) -- **fix(codex):** treat an OAuth 401 as an unrecoverable refresh failure (stop retrying a dead token). ([#4686](https://github.com/diegosouzapw/OmniRoute/pull/4686) — thanks @sacwooky / @diegosouzapw) +- **fix(codex):** 将 OAuth 401 视为不可恢复的刷新失败(停止重试已失效的 Token)。([#4686](https://github.com/diegosouzapw/OmniRoute/pull/4686) — 感谢 @sacwooky / @diegosouzapw) -- **fix(translator):** coerce tool descriptions to strings in OpenAI normalization. ([#4675](https://github.com/diegosouzapw/OmniRoute/pull/4675) — thanks @East-rayyy / @diegosouzapw) +- **fix(translator):** 在 OpenAI 规范化中将工具描述强制转换为字符串。([#4675](https://github.com/diegosouzapw/OmniRoute/pull/4675) — 感谢 @East-rayyy / @diegosouzapw) -- **fix(dashboard):** stop double-masking an already-masked API key in the list view (E2E 3/9 regression). ([#4671](https://github.com/diegosouzapw/OmniRoute/pull/4671) — thanks @diegosouzapw) +- **fix(dashboard): 停止在列表视图中对已脱敏的 API Key 进行二次脱敏(E2E 3/9 回归)。([#4671](https://github.com/diegosouzapw/OmniRoute/pull/4671) — 感谢 @diegosouzapw) -- **fix(combo):** flatten Anthropic tool messages + tool history to prevent an upstream 503. ([#4648](https://github.com/diegosouzapw/OmniRoute/pull/4648) — thanks @warelik / @diegosouzapw) +- **fix(combo): 展平 Anthropic 工具消息 + 工具历史以防止上游 503。([#4648](https://github.com/diegosouzapw/OmniRoute/pull/4648) — 感谢 @warelik / @diegosouzapw) -- **fix(providers):** require a Default Model in the compatible-provider API-key setup flow. ([#4641](https://github.com/diegosouzapw/OmniRoute/pull/4641) — thanks @arden1601) +- **fix(providers): 在兼容服务商 API-key 设置流程中要求设置默认模型。([#4641](https://github.com/diegosouzapw/OmniRoute/pull/4641) — 感谢 @arden1601) -### 🔒 Security +### 🔒 安全 -- **fix(auth):** only trust forwarding headers (`X-Forwarded-For` / `X-Real-IP`) from loopback TCP peers, so a non-loopback client can't spoof its origin to bypass local-only route guards. ([#4689](https://github.com/diegosouzapw/OmniRoute/pull/4689) — thanks @Jordannst / @diegosouzapw) +- **fix(auth):** 仅信任来自 loopback TCP 对端的转发头(`X-Forwarded-For` / `X-Real-IP`),使非 loopback 客户端无法伪造来源以绕过仅限本地路由的守卫。([#4689](https://github.com/diegosouzapw/OmniRoute/pull/4689) — 感谢 @Jordannst / @diegosouzapw) -- **fix(sse):** redact the API key from the AUTH debug log in the chat handler. ([#4858](https://github.com/diegosouzapw/OmniRoute/pull/4858) — thanks @sacwooky) +- **fix(sse):** 在聊天处理器的 AUTH 调试日志中脱敏 API Key。([#4858](https://github.com/diegosouzapw/OmniRoute/pull/4858) — 感谢 @sacwooky) -- **fix(oauth):** classify `/api/oauth/cursor/auto-import` as a local-only route in the route guard, so the loopback-enforced process-spawning endpoint can't be reached through a tunneled/leaked JWT (Hard Rule #17). ([#5070](https://github.com/diegosouzapw/OmniRoute/pull/5070) — thanks @diegosouzapw) +- **fix(oauth):** 在路由守卫中将 `/api/oauth/cursor/auto-import` 分类为仅限本地路由,使需要 loopback 执行的进程启动端点无法通过隧道/泄漏的 JWT 访问(第 17 号硬规则)。([#5070](https://github.com/diegosouzapw/OmniRoute/pull/5070) — 感谢 @diegosouzapw) -### 📝 Maintenance +### 📝 维护 -- **chore(ci):** harden the release flow — decouple the Quality Ratchet from coverage-shard flakes (`if: !cancelled()` + `--allow-missing`), add fast-path drift gates (`check:complexity`, `check:cognitive-complexity`, `check:pack-policy`, `check:build-scope`), and raise the default build heap to 8 GB. ([#5054](https://github.com/diegosouzapw/OmniRoute/pull/5054) — thanks @diegosouzapw) +- **chore(ci):** 加固发布流程 — 将质量递增门禁与覆盖率分片波动解耦(`if: !cancelled()` + `--allow-missing`),添加快速路径偏差门禁(`check:complexity`、`check:cognitive-complexity`、`check:pack-policy`、`check:build-scope`),并将默认构建堆内存提升至 8 GB。([#5054](https://github.com/diegosouzapw/OmniRoute/pull/5054) — 感谢 @diegosouzapw) -- **docs(routing):** sync the combo strategy docs for Fusion (17 strategies). ([#5067](https://github.com/diegosouzapw/OmniRoute/pull/5067) — thanks @diegosouzapw) +- **docs(routing):** 同步 Fusion 的 Combo 策略文档(17 策略)。([#5067](https://github.com/diegosouzapw/OmniRoute/pull/5067) — 感谢 @diegosouzapw) -- **test(sse):** golden-lock the `provider.ts` translate-path across all providers. ([#4734](https://github.com/diegosouzapw/OmniRoute/pull/4734) — thanks @diegosouzapw / @decolua) +- **test(sse):** 全网关锁定所有服务商的 `provider.ts` 翻译路径。([#4734](https://github.com/diegosouzapw/OmniRoute/pull/4734) — 感谢 @diegosouzapw / @decolua) -- **docs(env):** document `HEADROOM_URL` in `.env.example` + `ENVIRONMENT.md`. (thanks @diegosouzapw) +- **docs(env):** 在 `.env.example` + `ENVIRONMENT.md` 中记录 `HEADROOM_URL`。(感谢 @diegosouzapw) -- **chore(quality):** rebaseline the file-size ratchet across the rc17 PR-batch levas (leva2/leva3/leva4) to absorb cycle drift. (thanks @diegosouzapw) +- **chore(quality):** 在 rc17 PR 批次 leva(leva2/leva3/leva4)上重新校准文件大小门禁以吸收周期漂移。(感谢 @diegosouzapw) --- ## [3.8.36] — 2026-06-25 -### ✨ New Features +### ✨ 新功能 -**Quota-Share system** +**配额共享系统** -- **feat(quota):** introduce a dedicated `quota-share` combo strategy (Fase 3 #9) — Deficit Round Robin scheduling with per-model in-flight gating (P2C), automatic DB migration that promotes existing `qtSd/*` combos, and per-policy gating so invalid allocations cannot bleed `allow` to unintended connections. ([#4939](https://github.com/diegosouzapw/OmniRoute/pull/4939), [#4901](https://github.com/diegosouzapw/OmniRoute/pull/4901)) -- **feat(quota):** multi-window usage buckets, per-(key,model) caps, and session stickiness — connections now track consumption across 5 h, 7 d, and per-model windows; `quota_allocation_model_caps` enforces per-key/model limits; session stickiness preserves prompt-cache integrity across turns. ([#4928](https://github.com/diegosouzapw/OmniRoute/pull/4928), [#4927](https://github.com/diegosouzapw/OmniRoute/pull/4927), [#4929](https://github.com/diegosouzapw/OmniRoute/pull/4929)) -- **feat(quota):** headroom strategy + proactive saturation — new `headroom` combo strategy selects connections by available quota margin; universal proactive saturation via upstream token-usage response headers; real Claude quota saturation sourced from `/api/oauth/usage`. ([#4908](https://github.com/diegosouzapw/OmniRoute/pull/4908), [#4907](https://github.com/diegosouzapw/OmniRoute/pull/4907), [#4885](https://github.com/diegosouzapw/OmniRoute/pull/4885)) -- **feat(quota):** concurrency control + cooldown-wait (Fase 2.1) — `max_concurrent` is enforced at dispatch time; quota-share combos queue concurrent requests with a short cooldown-wait and re-dispatch on slot availability (Variant A); a cron heal proactively restores connections after their window resets. ([#4965](https://github.com/diegosouzapw/OmniRoute/pull/4965), [#4970](https://github.com/diegosouzapw/OmniRoute/pull/4970), [#4967](https://github.com/diegosouzapw/OmniRoute/pull/4967), [#4900](https://github.com/diegosouzapw/OmniRoute/pull/4900)) +- **feat(quota):** 引入专用的 `quota-share` Combo 策略(Fase 3 #9) — 带每模型飞行门控(P2C)的 Deficit Round Robin 调度、自动数据库迁移以升级现有 `qtSd/*` Combo,以及每策略门控使无效分配不会将 `allow` 泄漏到非目标连接。([#4939](https://github.com/diegosouzapw/OmniRoute/pull/4939), [#4901](https://github.com/diegosouzapw/OmniRoute/pull/4901)) +- **feat(quota):** 多窗口用量桶、每(key,model)上限和会话粘性 — 连接现在跟踪 5 小时、7 天和每模型窗口的消耗;`quota_allocation_model_caps` 强制执行每 key/模型限制;会话粘性在多轮对话中保持提示缓存完整性。([#4928](https://github.com/diegosouzapw/OmniRoute/pull/4928), [#4927](https://github.com/diegosouzapw/OmniRoute/pull/4927), [#4929](https://github.com/diegosouzapw/OmniRoute/pull/4929)) +- **feat(quota):** headroom 策略 + 主动饱和度 — 新的 `headroom` Combo 策略按可用配额余量选择连接;通过上游 Token 用量响应头进行通用主动饱和度计算;从 `/api/oauth/usage` 获取真实的 Claude 配额饱和度。([#4908](https://github.com/diegosouzapw/OmniRoute/pull/4908), [#4907](https://github.com/diegosouzapw/OmniRoute/pull/4907), [#4885](https://github.com/diegosouzapw/OmniRoute/pull/4885)) +- **feat(quota):** 并发控制 + 冷却等待(Fase 2.1) — `max_concurrent` 在调度时强制执行;quota-share Combo 通过短冷却等待对并发请求进行排队,并在槽位可用时重新调度(方案 A);定时修复任务在窗口重置后主动恢复连接。([#4965](https://github.com/diegosouzapw/OmniRoute/pull/4965), [#4970](https://github.com/diegosouzapw/OmniRoute/pull/4970), [#4967](https://github.com/diegosouzapw/OmniRoute/pull/4967), [#4900](https://github.com/diegosouzapw/OmniRoute/pull/4900)) -**Combo routing** +**Combo 路由** -- **feat(combo):** task-aware routing strategy — routes requests to the best-fit connection based on task-type metadata, enabling per-task provider specialization within a combo. ([#4945](https://github.com/diegosouzapw/OmniRoute/pull/4945)) -- **feat(combo):** Fusion strategy (16th strategy) — fan out to a configurable panel of models in parallel, then synthesize results through a judge model. ([#4652](https://github.com/diegosouzapw/OmniRoute/pull/4652)) -- **feat(combos):** add an editable per-combo `description` field. The routing-combo form now has a Description input, persisted in the combo `data` blob via `/api/combos` (POST/PUT) and round-tripped through GET — no new DB column. ([#5005](https://github.com/diegosouzapw/OmniRoute/issues/5005)) -- **feat(routing):** honor `X-Route-Model` request header to override `body.model`, enabling per-request model switching without modifying the request body. ([#4863](https://github.com/diegosouzapw/OmniRoute/pull/4863) — thanks @costaeder) +- **feat(combo):** 任务感知路由策略 — 根据任务类型元数据将请求路由到最匹配的连接,实现在 Combo 内的每任务服务商专业化。([#4945](https://github.com/diegosouzapw/OmniRoute/pull/4945)) +- **feat(combo):** Fusion 策略(第 16 个策略) — 并行广播到可配置的模型评审团,然后通过裁判模型综合结果。([#4652](https://github.com/diegosouzapw/OmniRoute/pull/4652)) +- **feat(combos):** 添加可编辑的每 Combo `description` 字段。路由 Combo 表单现在有 Description 输入框,通过 `/api/combos`(POST/PUT)持久化在 Combo `data` blob 中,并通过 GET 往返 — 无新增数据库列。([#5005](https://github.com/diegosouzapw/OmniRoute/issues/5005)) +- **feat(routing):** 遵循 `X-Route-Model` 请求头以覆盖 `body.model`,实现在不修改请求体的情况下进行每请求模型切换。([#4863](https://github.com/diegosouzapw/OmniRoute/pull/4863) — 感谢 @costaeder) -**Providers & models** +**服务商与模型** -- **feat(providers):** update volcengine-ark model list, adding DeepSeek-V4-Flash and DeepSeek-V4-Pro. ([#4905](https://github.com/diegosouzapw/OmniRoute/pull/4905) — thanks @kenlin8827) -- **feat(provider):** add CodeBuddy CN (`copilot.tencent.com`) — full OAuth + executor + model catalog stack. ([#4664](https://github.com/diegosouzapw/OmniRoute/pull/4664)) -- **feat(opencode-go):** advertise `glm-5.2` and `kimi-k2.7-code` to align with official Go endpoints. ([#4711](https://github.com/diegosouzapw/OmniRoute/pull/4711)) -- **feat(sse):** add Google Flow video-generation provider. ([#4769](https://github.com/diegosouzapw/OmniRoute/pull/4769)) -- **feat(api/v1):** include alias-backed models in the `/v1/models` listing. ([#4630](https://github.com/diegosouzapw/OmniRoute/pull/4630)) +- **feat(providers):** 更新 volcengine-ark 模型列表,新增 DeepSeek-V4-Flash 和 DeepSeek-V4-Pro。([#4905](https://github.com/diegosouzapw/OmniRoute/pull/4905) — 感谢 @kenlin8827) +- **feat(provider):** 新增 CodeBuddy CN(`copilot.tencent.com`)— 完整的 OAuth + 执行器 + 模型目录栈。([#4664](https://github.com/diegosouzapw/OmniRoute/pull/4664)) +- **feat(opencode-go):** 通告 `glm-5.2` 和 `kimi-k2.7-code` 以与官方 Go 端点保持一致。([#4711](https://github.com/diegosouzapw/OmniRoute/pull/4711)) +- **feat(sse):** 新增 Google Flow 视频生成服务商。([#4769](https://github.com/diegosouzapw/OmniRoute/pull/4769)) +- **feat(api/v1):** 在 `/v1/models` 列表中纳入别名支持的模型。([#4630](https://github.com/diegosouzapw/OmniRoute/pull/4630)) -**Proxy pool** +**代理池** -- **feat(proxy-pool):** Cloudflare Workers proxy deployer + pool integration — deploy Cloudflare Workers relays directly from the dashboard and register them in the proxy pool. ([#4640](https://github.com/diegosouzapw/OmniRoute/pull/4640)) -- **feat(proxy-pool):** Deno Deploy relays + group action buttons — deploy Deno Deploy relay workers and manage proxy groups with new bulk-action controls. ([#4643](https://github.com/diegosouzapw/OmniRoute/pull/4643)) +- **feat(proxy-pool):** Cloudflare Workers 代理部署器 + 池集成 — 直接从控制台部署 Cloudflare Workers 中继并将其注册到代理池中。([#4640](https://github.com/diegosouzapw/OmniRoute/pull/4640)) +- **feat(proxy-pool):** Deno Deploy 中继 + 分组操作按钮 — 部署 Deno Deploy 中继工作线程,并使用新的批量操作控件管理代理分组。([#4643](https://github.com/diegosouzapw/OmniRoute/pull/4643)) -**Compression & infrastructure** +**压缩与基础设施** -- **feat(compression):** Kiro/CodeWhisperer tool-result compression engine — dedicated compressor for Kiro/CodeWhisperer tool outputs integrated into the streaming pipeline. ([#4635](https://github.com/diegosouzapw/OmniRoute/pull/4635)) -- **feat(endpoint):** per-endpoint custom system prompt injection. A toggle + text field in the Endpoint settings card lets users inject a custom system prompt into every model request, applied via the existing system-prompt engine. Stored in settings DB. ([#5022](https://github.com/diegosouzapw/OmniRoute/pull/5022) — thanks @whale9820) -- **feat(live-ws):** allow non-loopback clients via `LIVE_WS_ALLOWED_HOSTS` env var, enabling multi-host setups to access the live WebSocket API. ([#4877](https://github.com/diegosouzapw/OmniRoute/pull/4877) — thanks @KooshaPari) -- **feat(db):** track API endpoint dimension on `usage_history` for per-endpoint cost and usage analytics. ([#4676](https://github.com/diegosouzapw/OmniRoute/pull/4676)) +- **feat(compression):** Kiro/CodeWhisperer 工具结果压缩引擎 — 专用于 Kiro/CodeWhisperer 工具输出的压缩器,集成到流式传输管线中。([#4635](https://github.com/diegosouzapw/OmniRoute/pull/4635)) +- **feat(endpoint):** 每端点自定义系统提示注入。端点设置卡片中的开关 + 文本字段允许用户将自定义系统提示注入到每个模型请求中,通过现有的系统提示引擎应用。存储在设置数据库中。([#5022](https://github.com/diegosouzapw/OmniRoute/pull/5022) — 感谢 @whale9820) +- **feat(live-ws):** 通过 `LIVE_WS_ALLOWED_HOSTS` 环境变量允许非回环客户端,使多主机部署可以访问实时 WebSocket API。([#4877](https://github.com/diegosouzapw/OmniRoute/pull/4877) — 感谢 @KooshaPari) +- **feat(db):** 在 `usage_history` 上跟踪 API 端点维度,用于每端点成本和用量分析。([#4676](https://github.com/diegosouzapw/OmniRoute/pull/4676)) --- -### 🔧 Bug Fixes +### 🔧 问题修复 -**Translator** +**翻译器** -- **fix(translator):** regroup parallel tool results to be adjacent to their originating assistant turn, fixing tool-message ordering for providers that require strict interleaving. ([#4882](https://github.com/diegosouzapw/OmniRoute/pull/4882)) -- **fix(translator):** preserve literal empty-string tool arguments in OpenAI-to-Claude streaming — they were previously dropped, causing tool calls to arrive with missing parameters. ([#4959](https://github.com/diegosouzapw/OmniRoute/pull/4959)) -- **fix(translator):** normalize tools to Anthropic-native shape for non-Anthropic providers, ensuring tool definitions pass validation regardless of the format at the call site. ([#4650](https://github.com/diegosouzapw/OmniRoute/pull/4650)) -- **fix(translator):** provider thinking compatibility — correct thinking-block serialization for DeepSeek and Gemini providers. ([#4946](https://github.com/diegosouzapw/OmniRoute/pull/4946)) -- **fix(translator):** emit `` close marker for Anthropic thinking blocks, fixing truncated reasoning output in streamed responses. ([#4633](https://github.com/diegosouzapw/OmniRoute/pull/4633)) -- **fix(translator):** normalize `developer` role to `system` for OpenAI-format providers. ([#4625](https://github.com/diegosouzapw/OmniRoute/pull/4625)) -- **fix(translator):** strip top-level `client_metadata` on the OpenAI passthrough path (port from 9router#1157). ([#4624](https://github.com/diegosouzapw/OmniRoute/pull/4624)) -- **fix(translator):** replay `reasoning_content` on plain Xiaomi MiMo turns (port from 9router#1321). ([#4639](https://github.com/diegosouzapw/OmniRoute/pull/4639)) +- **fix(translator):** 将并行工具结果重新分组到其原始助手轮次附近,修复对需要严格交错排列的服务商的工具消息排序问题。([#4882](https://github.com/diegosouzapw/OmniRoute/pull/4882)) +- **fix(translator):** 在 OpenAI 到 Claude 的流式传输中保留字面空字符串工具参数 — 之前这些参数被丢弃,导致工具调用到达时缺少参数。([#4959](https://github.com/diegosouzapw/OmniRoute/pull/4959)) +- **fix(translator):** 将工具规范化为 Anthropic 原生格式,适用于非 Anthropic 服务商,确保工具定义无论调用点的格式如何都能通过校验。([#4650](https://github.com/diegosouzapw/OmniRoute/pull/4650)) +- **fix(translator):** 服务商思考兼容性 — 修正 DeepSeek 和 Gemini 服务商的思考块序列化。([#4946](https://github.com/diegosouzapw/OmniRoute/pull/4946)) +- **fix(translator):** 为 Anthropic 思考块发出 `` 关闭标记,修复流式响应中推理输出被截断的问题。([#4633](https://github.com/diegosouzapw/OmniRoute/pull/4633)) +- **fix(translator):** 将 `developer` 角色规范化为 `system`,适用于 OpenAI 格式的服务商。([#4625](https://github.com/diegosouzapw/OmniRoute/pull/4625)) +- **fix(translator):** 在 OpenAI 直通路径上剥离顶层 `client_metadata`(从 9router#1157 移植)。([#4624](https://github.com/diegosouzapw/OmniRoute/pull/4624)) +- **fix(translator):** 在纯 Xiaomi MiMo 轮次中重放 `reasoning_content`(从 9router#1321 移植)。([#4639](https://github.com/diegosouzapw/OmniRoute/pull/4639)) -**Copilot / GitHub executor** +**Copilot / GitHub 执行器** -- **fix(copilot):** never route Gemini/Claude model variants to the `/responses` endpoint — these models require the chat-completions path only. ([#4627](https://github.com/diegosouzapw/OmniRoute/pull/4627)) -- **fix(github):** route Copilot Codex models to `/responses` (port from 9router#102). ([#4626](https://github.com/diegosouzapw/OmniRoute/pull/4626)) -- **fix(copilot,antigravity):** cap `maxOutputTokens` at 16384 to stop "Invalid Argument" 400 errors on high-token requests. ([#4636](https://github.com/diegosouzapw/OmniRoute/pull/4636)) -- **fix(codex):** drop non-standard `codex.*` streaming events that break `responses.stream` consumers. ([#4715](https://github.com/diegosouzapw/OmniRoute/pull/4715) — thanks @jeffer1312) +- **fix(copilot):** 永远不要将 Gemini/Claude 模型变体路由到 `/responses` 端点 — 这些模型仅需要 chat-completions 路径。([#4627](https://github.com/diegosouzapw/OmniRoute/pull/4627)) +- **fix(github):** 将 Copilot Codex 模型路由到 `/responses`(从 9router#102 移植)。([#4626](https://github.com/diegosouzapw/OmniRoute/pull/4626)) +- **fix(copilot,antigravity):** 将 `maxOutputTokens` 上限设为 16384,以阻止高令牌请求出现 "Invalid Argument" 400 错误。([#4636](https://github.com/diegosouzapw/OmniRoute/pull/4636)) +- **fix(codex):** 丢弃破坏 `responses.stream` 消费者的非标准 `codex.*` 流式事件。([#4715](https://github.com/diegosouzapw/OmniRoute/pull/4715) — 感谢 @jeffer1312) **Claude / Anthropic** -- **fix(claude):** omit `adaptive_thinking` and `output_config.effort` for Haiku model variants, which reject those parameters. ([#4661](https://github.com/diegosouzapw/OmniRoute/pull/4661)) -- **fix(claude):** skip `mcp__` tool-name cloaking and guard against missing `connectionId` to prevent crashes on Claude-native MCP tool calls. ([#4861](https://github.com/diegosouzapw/OmniRoute/pull/4861) — thanks @costaeder) -- **fix(claude-oauth):** respect `429` backoff headers on the Claude OAuth usage endpoint to reduce polling spam during quota checks. ([#4655](https://github.com/diegosouzapw/OmniRoute/pull/4655)) +- **fix(claude):** 对 Haiku 模型变体省略 `adaptive_thinking` 和 `output_config.effort`,因为这些变体拒绝这些参数。([#4661](https://github.com/diegosouzapw/OmniRoute/pull/4661)) +- **fix(claude):** 跳过 `mcp__` 工具名伪装并防范缺失的 `connectionId`,以防止在 Claude 原生 MCP 工具调用时崩溃。([#4861](https://github.com/diegosouzapw/OmniRoute/pull/4861) — 感谢 @costaeder) +- **fix(claude-oauth):** 在 Claude OAuth 用量端点上遵循 `429` 退避头,以减少配额检查期间的轮询垃圾请求。([#4655](https://github.com/diegosouzapw/OmniRoute/pull/4655)) -**Routing & SSE** +**路由与 SSE** -- **fix(sse):** fail over on `400` responses that carry rate-limit text in the body, not just on canonical `429` status codes. ([#4986](https://github.com/diegosouzapw/OmniRoute/pull/4986)) -- **fix(sse):** honor per-account proxy and fingerprint-rotation settings in the opencode executor. ([#4989](https://github.com/diegosouzapw/OmniRoute/pull/4989)) -- **fix(sse):** soft-penalize exhausted providers in auto-combo scoring instead of hard-excluding them, improving fallback resilience. ([#4990](https://github.com/diegosouzapw/OmniRoute/pull/4990)) -- **fix(sse):** drop the CCP pin when the pinned provider is durably unhealthy, with anti-flap logic to prevent oscillation. ([#4864](https://github.com/diegosouzapw/OmniRoute/pull/4864) — thanks @costaeder) -- **fix(combo):** fetch models dynamically from custom provider endpoints instead of relying on a static list. ([#4860](https://github.com/diegosouzapw/OmniRoute/pull/4860)) -- **fix(combo):** propagate the selected connection ID to fallback error responses so model lockout applies to the correct connection rather than the wrong fallback target. ([#4809](https://github.com/diegosouzapw/OmniRoute/pull/4809) — thanks @Chewji9875) -- **fix(sse):** skip third-party tool-name cloaking for Anthropic-native server tools to prevent naming conflicts. ([#4808](https://github.com/diegosouzapw/OmniRoute/pull/4808) — thanks @NomenAK) +- **fix(sse):** 对携带速率限制文本的 `400` 响应进行容灾切换,而不仅仅是对标准的 `429` 状态码。([#4986](https://github.com/diegosouzapw/OmniRoute/pull/4986)) +- **fix(sse):** 在 opencode 执行器中遵循每账户代理和指纹轮换设置。([#4989](https://github.com/diegosouzapw/OmniRoute/pull/4989)) +- **fix(sse):** 在 auto-combo 评分中对已耗尽的服务商进行软惩罚而非硬排除,改善容灾弹性。([#4990](https://github.com/diegosouzapw/OmniRoute/pull/4990)) +- **fix(sse):** 当被钉选的服务商持续不健康时取消 CCP 钉选,并带有防抖动逻辑以防止振荡。([#4864](https://github.com/diegosouzapw/OmniRoute/pull/4864) — 感谢 @costaeder) +- **fix(combo):** 从自定义服务商端点动态获取模型,而不是依赖静态列表。([#4860](https://github.com/diegosouzapw/OmniRoute/pull/4860)) +- **fix(combo):** 将选定的连接 ID 传播到容灾错误响应,使模型锁定应用于正确的连接而非错误的容灾目标。([#4809](https://github.com/diegosouzapw/OmniRoute/pull/4809) — 感谢 @Chewji9875) +- **fix(sse):** 对 Anthropic 原生服务器工具跳过第三方工具名伪装,以防止命名冲突。([#4808](https://github.com/diegosouzapw/OmniRoute/pull/4808) — 感谢 @NomenAK) -**Quota** +**配额** -- **fix(quota):** quota-exclusive `qtSd/*` connections now appear in `/v1/models` listings; EPSILON-threshold check no longer falsely blocks under-budget allocations. ([#4830](https://github.com/diegosouzapw/OmniRoute/pull/4830)) -- **fix(quota):** migration 107 correctly activates the `quota-share` strategy on existing `qtSd/*` combos. ([#4962](https://github.com/diegosouzapw/OmniRoute/pull/4962)) +- **fix(quota):** 配额独占的 `qtSd/*` 连接现在出现在 `/v1/models` 列表中;EPSILON 阈值检查不再错误地阻止预算不足的分配。([#4830](https://github.com/diegosouzapw/OmniRoute/pull/4830)) +- **fix(quota):** 迁移 107 正确地在现有 `qtSd/*` Combo 上激活 `quota-share` 策略。([#4962](https://github.com/diegosouzapw/OmniRoute/pull/4962)) -**API / responses** +**API / 响应** -- **fix(api):** parse the `/v1/responses` request body once instead of 3–4 times on the hot path, reducing per-request overhead. ([#4958](https://github.com/diegosouzapw/OmniRoute/pull/4958)) -- **fix(api):** evict stale in-memory rate-limit windows to stop a slow heap leak on long-running instances. ([#4957](https://github.com/diegosouzapw/OmniRoute/pull/4957)) -- **fix(api):** require authentication on the compression `run-telemetry` endpoint; document `OMNIROUTE_EVAL_CREDENTIALS` env var. ([#4796](https://github.com/diegosouzapw/OmniRoute/pull/4796)) -- **fix(api):** stop `GET /api/system/env/repair` returning HTTP `500` on packaged installs (it broke the onboarding wizard). `createRequire(import.meta.url)` ran at module top-level; once webpack bundles the route into the standalone build, `import.meta.url` is frozen to the build-machine path and `createRequire` throws during evaluation, so the whole route failed to load. `createRequire` is now resolved lazily inside the guarded `better-sqlite3` block, root-dir resolution falls back to `process.cwd()`, and the route passes an explicit `rootDir`. ([#5028](https://github.com/diegosouzapw/OmniRoute/pull/5028)) +- **fix(api):** 在热路径上仅解析 `/v1/responses` 请求体一次,而非 3-4 次,减少每请求开销。([#4958](https://github.com/diegosouzapw/OmniRoute/pull/4958)) +- **fix(api):** 驱逐过期的内存中速率限制窗口,以阻止长时间运行实例的缓慢堆泄漏。([#4957](https://github.com/diegosouzapw/OmniRoute/pull/4957)) +- **fix(api):** 在压缩 `run-telemetry` 端点上要求认证;记录 `OMNIROUTE_EVAL_CREDENTIALS` 环境变量。([#4796](https://github.com/diegosouzapw/OmniRoute/pull/4796)) +- **fix(api):** 停止 `GET /api/system/env/repair` 在打包安装时返回 HTTP `500`(它破坏了入门向导)。`createRequire(import.meta.url)` 在模块顶层运行;一旦 webpack 将路由打包到独立构建中,`import.meta.url` 被冻结为构建机器路径,`createRequire` 在求值时抛出异常,因此整个路由加载失败。`createRequire` 现在在受保护的 `better-sqlite3` 块内惰性解析,根目录解析回退到 `process.cwd()`,并且路由传递显式的 `rootDir`。([#5028](https://github.com/diegosouzapw/OmniRoute/pull/5028)) -**Dashboard** +**仪表盘** -- **fix(dashboard):** show custom provider given-name instead of internal id across dashboard pages — cache, combo health, compression analytics, cost overview, health/autopilot, provider stats, route explainability, provider utilization, runtime. Adds shared `resolveProviderName` resolver and `useProviderNodeMap` hook. (#4603) -- **fix(dashboard):** on OAuth providers (e.g. GLM Coding), "Test all models" with auto-hide-failed now switches the model list to the "visible" filter after the run, so just-hidden failed models actually disappear on-screen — parity with the passthrough-provider path (#3610). Previously they were hidden in the DB but stayed visible under the "All" filter, so it looked like nothing was hidden. (#4887) -- **fix(dashboard):** restore the home-page provider topology card that was hidden by a default state change in #4596. ([#4963](https://github.com/diegosouzapw/OmniRoute/pull/4963)) -- **fix(dashboard):** proxy-pool success gating, sync-timestamp persistence, and opt-in Redis backend. ([#4988](https://github.com/diegosouzapw/OmniRoute/pull/4988)) -- **fix(dashboard):** show custom vision models in the LLM selector dropdown. ([#4653](https://github.com/diegosouzapw/OmniRoute/pull/4653)) +- **fix(dashboard):** 在仪表盘各页面显示自定义服务商的自定义名称而非内部 ID — 缓存、Combo 健康、压缩分析、成本概览、健康/自动驾驶、服务商统计、路由可解释性、服务商利用率、运行时。添加共享的 `resolveProviderName` 解析器和 `useProviderNodeMap` 钩子。(#4603) +- **fix(dashboard):** 在 OAuth 服务商(例如 GLM Coding)上,"测试所有模型" 并自动隐藏失败的模型现在在运行后将模型列表切换到 "可见" 过滤器,使刚隐藏的失败模型在屏幕上真正消失 — 与直通服务商路径保持一致 (#3610)。之前它们在数据库中被隐藏但在 "全部" 过滤器下仍然可见,因此看起来好像没有被隐藏。(#4887) +- **fix(dashboard):** 恢复因 #4596 中默认状态变更而被隐藏的首页服务商拓扑卡片。([#4963](https://github.com/diegosouzapw/OmniRoute/pull/4963)) +- **fix(dashboard):** 代理池成功门控、同步时间戳持久化以及可选的 Redis 后端。([#4988](https://github.com/diegosouzapw/OmniRoute/pull/4988)) +- **fix(dashboard):** 在 LLM 选择器下拉菜单中显示自定义视觉模型。([#4653](https://github.com/diegosouzapw/OmniRoute/pull/4653)) -**Providers** +**服务商** -- **fix(pollinations):** stop forcing `jsonMode` on every request. Pollinations treats `jsonMode=true` as "the model MUST return JSON" and rejects (HTTP 400 "messages must contain the word 'json'") any normal chat request whose messages don't mention "json", so all non-JSON chat was broken. `jsonMode` is now only enabled when the caller actually requests JSON output (`response_format.type` of `json_object` or `json_schema`). (#3981) -- **fix(antigravity):** default `safetySettings` to all-OFF for parity with the native Gemini paths. The Antigravity (Google Cloud Code) request builder set `safetySettings: undefined`, which `JSON.stringify` drops — so no safety settings reached Google and its server-side defaults false-flagged benign technical prompts as `prohibited_content` (HTTP 200 + blocked body, which combo failover treats as terminal). Now honors a caller-supplied value and otherwise defaults to `DEFAULT_SAFETY_SETTINGS`, matching the claude-to-gemini / openai-to-gemini paths. (#5003) -- **fix(antigravity):** exclude the standard Gemini rate-limit message from quota-exhaustion keyword matching to prevent false-positive saturation flags. ([#4810](https://github.com/diegosouzapw/OmniRoute/pull/4810) — thanks @Chewji9875) -- **fix(chatgpt-web):** map the advertised `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4-pro` and `gpt-5.2-pro` catalog ids to their dash-form ChatGPT backend slugs. They were missing from `MODEL_MAP`, so the executor sent the dot-form id verbatim, which the ChatGPT backend silently ignored and served the default Plus model instead of the requested one. Adds a drift guard asserting no advertised dot-form id reaches the backend verbatim. (#4665) -- **fix(gemini):** preserve the `pattern` field in the Antigravity tool schema sanitizer to avoid stripping valid regex constraints from tool definitions. ([#4651](https://github.com/diegosouzapw/OmniRoute/pull/4651)) -- **fix(opencode):** preserve DeepSeek reasoning content in streamed responses. ([#4631](https://github.com/diegosouzapw/OmniRoute/pull/4631)) -- **fix(perplexity):** validate API keys via the `/v1/models` endpoint instead of issuing a full chat request. ([#4654](https://github.com/diegosouzapw/OmniRoute/pull/4654)) -- **fix(qoder):** exchange PAT for `jt-*` job token before initiating Cosy chat, fixing auth failures after the Qoder credential format change. ([#4884](https://github.com/diegosouzapw/OmniRoute/pull/4884)) -- **fix(executors):** strip parameters unsupported by the target provider/model to prevent `400 Invalid parameter` errors on strict endpoints. ([#4658](https://github.com/diegosouzapw/OmniRoute/pull/4658)) -- **fix(executors):** preserve literal `reasoning_effort: "max"` for Ollama Cloud instead of normalizing to `xhigh`. Ollama Cloud accepts `high|medium|low|max|none` and rejects `xhigh` (`invalid reasoning value: 'xhigh'`); OpenRouter DeepSeek `max→xhigh` normalization is unaffected. ([#4993](https://github.com/diegosouzapw/OmniRoute/pull/4993) — thanks @Thinkscape) -- **fix(headroom):** translate openai-responses input through OpenAI for external compression. `adaptBodyForCompression` now serialises `function_call_output` items whose `output` field is a JSON object (not a string) so compression engines can process the content — previously those items were excluded from compression because `hasTextContent()` returned false for object values. ([#5023](https://github.com/diegosouzapw/OmniRoute/pull/5023) — thanks @anki1kr) -- **fix(proxy):** fan out direct dispatcher streams to all registered proxy endpoints. ([#4803](https://github.com/diegosouzapw/OmniRoute/pull/4803) — thanks @makcimbx) +- **fix(pollinations):** 停止对每个请求强制启用 `jsonMode`。Pollinations 将 `jsonMode=true` 视为"模型必须返回 JSON"并拒绝(HTTP 400 "messages must contain the word 'json'")任何消息中不包含 "json" 的普通聊天请求,因此所有非 JSON 聊天均被破坏。`jsonMode` 现在仅在调用者实际请求 JSON 输出(`response_format.type` 为 `json_object` 或 `json_schema`)时才启用。(#3981) +- **fix(antigravity):** 将 `safetySettings` 默认设为全部关闭,与原生 Gemini 路径保持一致。Antigravity(Google Cloud Code)请求构建器将 `safetySettings` 设为 `undefined`,`JSON.stringify` 会将其丢弃 — 因此没有安全设置到达 Google,其服务器端默认值将无害的技术提示误标记为 `prohibited_content`(HTTP 200 + 被阻止的正文,Combo 容灾将其视为终端错误)。现在会遵循调用者提供的值,否则默认使用 `DEFAULT_SAFETY_SETTINGS`,与 claude-to-gemini / openai-to-gemini 路径保持一致。(#5003) +- **fix(antigravity):** 从配额耗尽关键词匹配中排除标准的 Gemini 速率限制消息,以防止误报饱和度标记。([#4810](https://github.com/diegosouzapw/OmniRoute/pull/4810) — 感谢 @Chewji9875) +- **fix(chatgpt-web):** 将通告的 `gpt-5.5`、`gpt-5.5-pro`、`gpt-5.4-pro` 和 `gpt-5.2-pro` 目录 ID 映射到其 dash 格式的 ChatGPT 后端标识。它们之前缺失于 `MODEL_MAP`,因此执行器逐字发送点号格式的 ID,ChatGPT 后端静默忽略并返回默认的 Plus 模型而非所请求的模型。添加了一个漂移守卫,断言没有任何通告的点号格式 ID 逐字到达后端。(#4665) +- **fix(gemini):** 在 Antigravity 工具 Schema 清理器中保留 `pattern` 字段,以避免从工具定义中剥离有效的正则约束。([#4651](https://github.com/diegosouzapw/OmniRoute/pull/4651)) +- **fix(opencode):** 在流式响应中保留 DeepSeek 推理内容。([#4631](https://github.com/diegosouzapw/OmniRoute/pull/4631)) +- **fix(perplexity):** 通过 `/v1/models` 端点验证 API 密钥,而非发起完整的聊天请求。([#4654](https://github.com/diegosouzapw/OmniRoute/pull/4654)) +- **fix(qoder):** 在发起 Cosy 聊天之前将 PAT 兑换为 `jt-*` 作业令牌,修复 Qoder 凭证格式变更后的认证失败。([#4884](https://github.com/diegosouzapw/OmniRoute/pull/4884)) +- **fix(executors):** 剥离目标服务商/模型不支持的参数,以防止在严格端点上出现 `400 Invalid parameter` 错误。([#4658](https://github.com/diegosouzapw/OmniRoute/pull/4658)) +- **fix(executors):** 对 Ollama Cloud 保留字面 `reasoning_effort: "max"` 而非规范化为 `xhigh`。Ollama Cloud 接受 `high|medium|low|max|none` 并拒绝 `xhigh`(`invalid reasoning value: 'xhigh'`);OpenRouter DeepSeek 的 `max→xhigh` 规范化不受影响。([#4993](https://github.com/diegosouzapw/OmniRoute/pull/4993) — 感谢 @Thinkscape) +- **fix(headroom):** 通过 OpenAI 翻译 openai-responses 输入以进行外部压缩。`adaptBodyForCompression` 现在序列化其 `output` 字段为 JSON 对象(而非字符串)的 `function_call_output` 条目,使压缩引擎能够处理内容 — 之前这些条目因 `hasTextContent()` 对对象值返回 false 而被排除在压缩之外。([#5023](https://github.com/diegosouzapw/OmniRoute/pull/5023) — 感谢 @anki1kr) +- **fix(proxy):** 将直接调度器流扇出到所有已注册的代理端点。([#4803](https://github.com/diegosouzapw/OmniRoute/pull/4803) — 感谢 @makcimbx) -**Compression** +**压缩** -- **fix(compression):** eliminate ReDoS in the `math_inline` preservation regex — the previous pattern could catastrophically backtrack on untrusted input. ([#4838](https://github.com/diegosouzapw/OmniRoute/pull/4838)) -- **fix(compression):** stop RTK over-truncating file-read tool results — RTK now respects the full content length for file-read outputs. ([#4987](https://github.com/diegosouzapw/OmniRoute/pull/4987)) +- **fix(compression):** 消除 `math_inline` 保留正则中的 ReDoS — 之前的模式可能在不受信任的输入上发生灾难性回溯。([#4838](https://github.com/diegosouzapw/OmniRoute/pull/4838)) +- **fix(compression):** 停止 RTK 过度截断文件读取工具结果 — RTK 现在对文件读取输出尊重完整内容长度。([#4987](https://github.com/diegosouzapw/OmniRoute/pull/4987)) -**Build / CLI / infrastructure** +**构建 / CLI / 基础设施** -- **fix(build):** drop `@omniroute/open-sse` from `optimizePackageImports` to fix the Next.js build OOM crash. ([#4968](https://github.com/diegosouzapw/OmniRoute/pull/4968)) -- **fix(cli):** SIGKILL the systray child PID before closing the IPC channel to prevent macOS NSStatusItem orphan processes. ([#4732](https://github.com/diegosouzapw/OmniRoute/pull/4732)) -- **fix(cli):** bump `better-sqlite3` runtime pin to 12.10.1 for Node 26 compatibility. ([#4685](https://github.com/diegosouzapw/OmniRoute/pull/4685)) -- **fix(cli):** harden the systray2 tray runtime (port from 9router#1080). ([#4628](https://github.com/diegosouzapw/OmniRoute/pull/4628)) -- **fix(cli-tools):** tolerate JSONC (comments and trailing commas) in tool settings files. ([#4659](https://github.com/diegosouzapw/OmniRoute/pull/4659)) -- **fix(install):** make the `transformers` dependency optional so CUDA-host installs that lack Python bindings succeed. ([#4807](https://github.com/diegosouzapw/OmniRoute/pull/4807) — thanks @megamen32) -- **fix(db):** correct storage tuning settings to prevent WAL runaway on high-write workloads. ([#4834](https://github.com/diegosouzapw/OmniRoute/pull/4834) — thanks @rdself) -- **fix(image):** prevent compatible nodes from shadowing provider aliases in the image routing table. ([#4656](https://github.com/diegosouzapw/OmniRoute/pull/4656)) +- **fix(build):** 从 `optimizePackageImports` 中移除 `@omniroute/open-sse` 以修复 Next.js 构建 OOM 崩溃。([#4968](https://github.com/diegosouzapw/OmniRoute/pull/4968)) +- **fix(cli):** 在关闭 IPC 通道之前 SIGKILL systray 子进程 PID,以防止 macOS NSStatusItem 孤儿进程。([#4732](https://github.com/diegosouzapw/OmniRoute/pull/4732)) +- **fix(cli):** 将 `better-sqlite3` 运行时版本锁定提升至 12.10.1 以获得 Node 26 兼容性。([#4685](https://github.com/diegosouzapw/OmniRoute/pull/4685)) +- **fix(cli):** 加固 systray2 托盘运行时(从 9router#1080 移植)。([#4628](https://github.com/diegosouzapw/OmniRoute/pull/4628)) +- **fix(cli-tools):** 在工具设置文件中容忍 JSONC(注释和尾随逗号)。([#4659](https://github.com/diegosouzapw/OmniRoute/pull/4659)) +- **fix(install):** 使 `transformers` 依赖项变为可选,使缺乏 Python 绑定的 CUDA 主机安装能够成功。([#4807](https://github.com/diegosouzapw/OmniRoute/pull/4807) — 感谢 @megamen32) +- **fix(db):** 修正存储调优设置以防止高写入负载下 WAL 失控。([#4834](https://github.com/diegosouzapw/OmniRoute/pull/4834) — 感谢 @rdself) +- **fix(image):** 防止兼容节点在图像路由表中遮蔽服务商别名。([#4656](https://github.com/diegosouzapw/OmniRoute/pull/4656)) -**Plugin** +**插件** -- **fix(plugin):** opencode `auth.json` dual-key fallback for the auto-prefix migration. The config hook now looks up both the prefixed (`opencode-omniroute`) and bare (`omniroute`) keys, so users who authenticated before the `opencode-` prefix landed no longer need to re-auth. ([#5027](https://github.com/diegosouzapw/OmniRoute/pull/5027) — thanks @herjarsa) +- **fix(plugin):** opencode `auth.json` 双键容灾以支持自动前缀迁移。配置钩子现在同时查找带前缀的(`opencode-omniroute`)和裸的(`omniroute`)键,因此在 `opencode-` 前缀落地之前认证的用户不再需要重新认证。([#5027](https://github.com/diegosouzapw/OmniRoute/pull/5027) — 感谢 @herjarsa) --- -### 🔒 Security +### 🔒 安全 -- **fix(security):** block SSRF allowlist bypass via `x-relay-path` header manipulation on Deno/Vercel relays. ([#4899](https://github.com/diegosouzapw/OmniRoute/pull/4899)) -- **fix(security):** pin image-fetch DNS resolution to prevent SSRF DNS-rebinding attacks (GHSA-cmhj-wh2f-9cgx). ([#4634](https://github.com/diegosouzapw/OmniRoute/pull/4634)) -- **fix(security):** do not trust the loopback socket as local-only when the server is behind a reverse proxy, closing a potential auth bypass path. ([#4632](https://github.com/diegosouzapw/OmniRoute/pull/4632)) -- **fix(security):** validate the Kiro region parameter to prevent SSRF via crafted region strings (GHSA-6mwv-4mrm-5p3m). ([#4629](https://github.com/diegosouzapw/OmniRoute/pull/4629)) -- **fix(copilot):** replace `execSync` shell interpolation with `execFile` in the `runOmniRouteCli` tool to prevent command injection. The user-supplied command is now split into an argv array and passed to `execFile` (no shell), so shell metacharacters are treated as literal text; error output is routed through `sanitizeErrorMessage()`. ([#5024](https://github.com/diegosouzapw/OmniRoute/pull/5024) — thanks @hamsa0x7) +- **fix(security):** 阻止通过 Deno/Vercel 中继上的 `x-relay-path` 头操纵绕过 SSRF 白名单。([#4899](https://github.com/diegosouzapw/OmniRoute/pull/4899)) +- **fix(security):** 固定图像获取 DNS 解析以防止 SSRF DNS 重新绑定攻击(GHSA-cmhj-wh2f-9cgx)。([#4634](https://github.com/diegosouzapw/OmniRoute/pull/4634)) +- **fix(security):** 当服务器位于反向代理之后时,不信任回环套接字为仅本地,堵住潜在的认证绕过路径。([#4632](https://github.com/diegosouzapw/OmniRoute/pull/4632)) +- **fix(security):** 验证 Kiro 区域参数以防止通过伪造的区域字符串进行 SSRF(GHSA-6mwv-4mrm-5p3m)。([#4629](https://github.com/diegosouzapw/OmniRoute/pull/4629)) +- **fix(copilot):** 在 `runOmniRouteCli` 工具中用 `execFile` 替换 `execSync` Shell 插值以防止命令注入。用户提供的命令现在被拆分为 argv 数组并传递给 `execFile`(无 Shell),因此 Shell 元字符被视为字面文本;错误输出通过 `sanitizeErrorMessage()` 路由。([#5024](https://github.com/diegosouzapw/OmniRoute/pull/5024) — 感谢 @hamsa0x7) --- -### 📝 Maintenance +### 📝 维护 -**God-file decomposition (continued, #3501)** +**God-file 分解(持续进行,#3501)** -- **refactor(chatCore):** extracted 12 focused helpers from `chatCore.ts` covering the streaming pipeline (`assembleStreamingPipeline`), cache-store logic (`storeStreamingSemanticCacheResponse`, `storeSemanticCacheResponse`), response headers (`assembleStreamingResponseHeaders`, `buildNonStreamingResponseHeaders`), JSON→SSE bridge (`maybeConvertJsonBodyToSse`), guardrail context (`buildPostCallGuardrailContext`), usage buffer (`applyClientUsageBuffer`), plugin hook (`runPluginOnRequestHook`), analytics (`writeCompressionAnalytics`, `emitOutputStyleTelemetry`), and compression predicates/settings (`resolveCompressionSettings`, et al.). ([#4811](https://github.com/diegosouzapw/OmniRoute/pull/4811)–[#4837](https://github.com/diegosouzapw/OmniRoute/pull/4837)) -- **refactor(sse/db/api):** continued decomposition of `services/usage.ts` (extracted quota-core, scalar/format helpers, Antigravity/GLM/MiniMax usage families), `db/core.ts` (schema-column reconciliation, snake↔camel column-mapping), `db/apiKeys.ts` (row-parsers, model-permission matching), and `validation.ts` (URL/headers/transport leaf layer, web-cookie/Meta-AI validators, enterprise-cloud + probe, audio/speech/apikey, search/embedding/rerank, and OpenAI/Anthropic format validators). ([#4921](https://github.com/diegosouzapw/OmniRoute/pull/4921)–[#4956](https://github.com/diegosouzapw/OmniRoute/pull/4956)) -- **refactor(pricing/providers):** decomposed `pricing.ts` into shared tiers + partitioned `DEFAULT_PRICING` modules, and split the `providers.ts` catalog into semantic data modules organized by provider family. ([#4917](https://github.com/diegosouzapw/OmniRoute/pull/4917), [#4918](https://github.com/diegosouzapw/OmniRoute/pull/4918)) -- **refactor(open-sse):** extract `safeParseJSON` utility and dedup `tryParseJSON` call sites; extract and dedup the fallback `tool_call` ID generation helper. ([#4735](https://github.com/diegosouzapw/OmniRoute/pull/4735), [#4736](https://github.com/diegosouzapw/OmniRoute/pull/4736)) +- **refactor(chatCore):** 从 `chatCore.ts` 中提取了 12 个聚焦的辅助函数,涵盖流式管线(`assembleStreamingPipeline`)、缓存存储逻辑(`storeStreamingSemanticCacheResponse`、`storeSemanticCacheResponse`)、响应头(`assembleStreamingResponseHeaders`、`buildNonStreamingResponseHeaders`)、JSON→SSE 桥接(`maybeConvertJsonBodyToSse`)、护栏上下文(`buildPostCallGuardrailContext`)、用量缓冲(`applyClientUsageBuffer`)、插件钩子(`runPluginOnRequestHook`)、分析(`writeCompressionAnalytics`、`emitOutputStyleTelemetry`)以及压缩谓词/设置(`resolveCompressionSettings` 等)。([#4811](https://github.com/diegosouzapw/OmniRoute/pull/4811)–[#4837](https://github.com/diegosouzapw/OmniRoute/pull/4837)) +- **refactor(sse/db/api):** 持续分解 `services/usage.ts`(提取了配额核心、标量/格式辅助函数、Antigravity/GLM/MiniMax 用量系列)、`db/core.ts`(Schema-列对账、snake↔camel 列映射)、`db/apiKeys.ts`(行解析器、模型权限匹配)以及 `validation.ts`(URL/请求头/传输叶层、web-cookie/Meta-AI 校验器、企业云 + 探针、音频/语音/API 密钥、搜索/嵌入/重排序以及 OpenAI/Anthropic 格式校验器)。([#4921](https://github.com/diegosouzapw/OmniRoute/pull/4921)–[#4956](https://github.com/diegosouzapw/OmniRoute/pull/4956)) +- **refactor(pricing/providers):** 将 `pricing.ts` 分解为共享层级 + 分区的 `DEFAULT_PRICING` 模块,并将 `providers.ts` 目录拆分为按服务商系列组织的语义数据模块。([#4917](https://github.com/diegosouzapw/OmniRoute/pull/4917), [#4918](https://github.com/diegosouzapw/OmniRoute/pull/4918)) +- **refactor(open-sse):** 提取 `safeParseJSON` 工具函数并去重 `tryParseJSON` 调用点;提取并去重容灾 `tool_call` ID 生成辅助函数。([#4735](https://github.com/diegosouzapw/OmniRoute/pull/4735), [#4736](https://github.com/diegosouzapw/OmniRoute/pull/4736)) -**Quality & CI** +**质量与 CI** -- **chore(quality):** release base-red reconciliation + ratchet rebaselines — file-size, env-doc, and catalog baseline updates across multiple gates. ([#4630](https://github.com/diegosouzapw/OmniRoute/pull/4630), [#4879](https://github.com/diegosouzapw/OmniRoute/pull/4879), [#4886](https://github.com/diegosouzapw/OmniRoute/pull/4886), [#4915](https://github.com/diegosouzapw/OmniRoute/pull/4915), [#4961](https://github.com/diegosouzapw/OmniRoute/pull/4961), [#4973](https://github.com/diegosouzapw/OmniRoute/pull/4973)) -- **ci(quality):** shift heavy validation gates to the PR→release merge fast-path to accelerate the release cycle. ([#4857](https://github.com/diegosouzapw/OmniRoute/pull/4857)) -- **fix(ci):** include `coverage/lcov.info` in the coverage-report artifact so SonarQube can consume it. ([#4670](https://github.com/diegosouzapw/OmniRoute/pull/4670)) -- **fix(test):** validate Anthropic-compatible connections via `POST /v1/messages` for accurate connectivity testing. ([#4657](https://github.com/diegosouzapw/OmniRoute/pull/4657)) +- **chore(quality):** 发布基准-红色对账 + 棘轮基准重置 — 跨多个门控的文件大小、环境文档和目录基线更新。([#4630](https://github.com/diegosouzapw/OmniRoute/pull/4630), [#4879](https://github.com/diegosouzapw/OmniRoute/pull/4879), [#4886](https://github.com/diegosouzapw/OmniRoute/pull/4886), [#4915](https://github.com/diegosouzapw/OmniRoute/pull/4915), [#4961](https://github.com/diegosouzapw/OmniRoute/pull/4961), [#4973](https://github.com/diegosouzapw/OmniRoute/pull/4973)) +- **ci(quality):** 将重量级校验门控移至 PR→发布合并快速路径,以加速发布周期。([#4857](https://github.com/diegosouzapw/OmniRoute/pull/4857)) +- **fix(ci):** 在覆盖率报告产物中包含 `coverage/lcov.info`,以便 SonarQube 可以消费它。([#4670](https://github.com/diegosouzapw/OmniRoute/pull/4670)) +- **fix(test):** 通过 `POST /v1/messages` 验证 Anthropic 兼容连接,以获得准确的连接性测试。([#4657](https://github.com/diegosouzapw/OmniRoute/pull/4657)) -**Docs** +**文档** -- **docs(resilience):** document Quota-Share Concurrency Control — `max_concurrent` enforcement, serialization behavior, and cooldown-wait semantics. ([#4980](https://github.com/diegosouzapw/OmniRoute/pull/4980)) -- **docs(perf):** add per-endpoint p50/p95/p99 latency and cost budgets reference. ([#4867](https://github.com/diegosouzapw/OmniRoute/pull/4867) — thanks @KooshaPari) -- **docs(ops):** add canonical incident response runbook. ([#4868](https://github.com/diegosouzapw/OmniRoute/pull/4868) — thanks @KooshaPari) -- **docs(ops):** document the release-green family — `green-prs`, `check:release-green`, `babysit`, and nightly gate workflows. ([#4679](https://github.com/diegosouzapw/OmniRoute/pull/4679)) -- **docs(agentbridge):** document Electron `NODE_EXTRA_CA_CERTS`, real model IDs, and identity caveat for agent bridge integrations. ([#4718](https://github.com/diegosouzapw/OmniRoute/pull/4718)) -- **docs:** clarify Kiro provides ~50 credits/month per account, not unlimited. ([#4690](https://github.com/diegosouzapw/OmniRoute/pull/4690)) +- **docs(resilience):** 记录配额共享并发控制 — `max_concurrent` 强制执行、序列化行为以及冷却等待语义。([#4980](https://github.com/diegosouzapw/OmniRoute/pull/4980)) +- **docs(perf):** 添加每端点 p50/p95/p99 延迟和成本预算参考。([#4867](https://github.com/diegosouzapw/OmniRoute/pull/4867) — 感谢 @KooshaPari) +- **docs(ops):** 添加标准事件响应操作手册。([#4868](https://github.com/diegosouzapw/OmniRoute/pull/4868) — 感谢 @KooshaPari) +- **docs(ops):** 记录发布绿色系列 — `green-prs`、`check:release-green`、`babysit` 和夜间门控工作流。([#4679](https://github.com/diegosouzapw/OmniRoute/pull/4679)) +- **docs(agentbridge):** 记录 Electron `NODE_EXTRA_CA_CERTS`、真实模型 ID 以及代理桥接集成的身份注意事项。([#4718](https://github.com/diegosouzapw/OmniRoute/pull/4718)) +- **docs:** 澄清 Kiro 每账户每月提供约 50 积分,而非无限。([#4690](https://github.com/diegosouzapw/OmniRoute/pull/4690)) -**Misc** +**杂项** -- **chore(claude,codex):** bump pinned CLI identities — Claude `2.1.158 → 2.1.187`, Codex `0.132.0 → 0.142.0`. ([#4883](https://github.com/diegosouzapw/OmniRoute/pull/4883)) -- **chore(dashboard):** rename Qoder display label from "Qoder AI" to "Qoder". ([#4733](https://github.com/diegosouzapw/OmniRoute/pull/4733)) +- **chore(claude,codex):** 提升固定的 CLI 身份版本 — Claude `2.1.158 → 2.1.187`,Codex `0.132.0 → 0.142.0`。([#4883](https://github.com/diegosouzapw/OmniRoute/pull/4883)) +- **chore(dashboard):** 将 Qoder 显示标签从 "Qoder AI" 重命名为 "Qoder"。([#4733](https://github.com/diegosouzapw/OmniRoute/pull/4733)) --- ## [3.8.35] — 2026-06-23 -### ✨ New Features +### ✨ 新功能 -- **Adaptive context compression (Phase 4)**: a four-layer compression upgrade landed across stacked PRs — an **Output Styles** registry (`terse-prose` / `less-code` / `terse-cjk`) ([#4694](https://github.com/diegosouzapw/OmniRoute/pull/4694) — thanks @diegosouzapw), an opt-in **SLM `ultra` tier** (two-tier LLMLingua with heuristic fallback) ([#4707](https://github.com/diegosouzapw/OmniRoute/pull/4707) — thanks @diegosouzapw), a **context-budget adaptive dial** (reserve-output ladder + floor) ([#4716](https://github.com/diegosouzapw/OmniRoute/pull/4716) — thanks @diegosouzapw), and an **offline evaluation harness** (PII-gated corpus, self-test judge, gold-grader, real-pipeline runner behind a `ModelClient` seam) ([#4720](https://github.com/diegosouzapw/OmniRoute/pull/4720) — thanks @diegosouzapw). All four layers share a single `CompressionRunTelemetry` contract. -- **Redoc-rendered API docs**: a consolidated OpenAPI spec now lives at `docs/openapi.yaml` and is served as interactive Redoc documentation at `/api/docs`. ([#4781](https://github.com/diegosouzapw/OmniRoute/pull/4781) — thanks @KooshaPari / @diegosouzapw) +- **自适应上下文压缩(第四阶段)**:四层压缩升级通过堆叠式 PR 实现——**输出风格**注册表(`terse-prose` / `less-code` / `terse-cjk`)([#4694](https://github.com/diegosouzapw/OmniRoute/pull/4694) — 感谢 @diegosouzapw),可选的 **SLM `ultra` 层级**(两级 LLMLingua 配合启发式容灾)([#4707](https://github.com/diegosouzapw/OmniRoute/pull/4707) — 感谢 @diegosouzapw),**上下文预算自适应拨盘**(预留输出阶梯 + 下限)([#4716](https://github.com/diegosouzapw/OmniRoute/pull/4716) — 感谢 @diegosouzapw),以及**离线评估工具集**(PII 门控语料库、自测评判器、黄金评分器、通过 `ModelClient` 接缝的真实管线运行器)([#4720](https://github.com/diegosouzapw/OmniRoute/pull/4720) — 感谢 @diegosouzapw)。四个层级共享同一 `CompressionRunTelemetry` 契约。 +- **Redoc 渲染的 API 文档**:合并后的 OpenAPI 规范现位于 `docs/openapi.yaml`,并作为交互式 Redoc 文档在 `/api/docs` 上提供。([#4781](https://github.com/diegosouzapw/OmniRoute/pull/4781) — 感谢 @KooshaPari / @diegosouzapw) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **db-backups**: make the database-import size cap configurable via `OMNIROUTE_DB_IMPORT_MAX_MB` (default 100 MB, 4 GB ceiling) so backups larger than 100 MB can be restored; error message now points to the env var and to VACUUM ([#4757](https://github.com/diegosouzapw/OmniRoute/pull/4757) — closes #4719, thanks @diegosouzapw). -- **Onboarding**: add the missing `onboarding.tiers` step-title translation so the setup wizard no longer crashes with `MISSING_MESSAGE: onboarding.tiers` ([#4755](https://github.com/diegosouzapw/OmniRoute/pull/4755) — closes #4698, thanks @diegosouzapw). -- **deepseek-web**: fold `role:"tool"` results into the single-prompt transcript (`messagesToPrompt`) so tool outputs reach the model instead of being silently dropped when a follow-up turn omits the `tools[]` array ([#4756](https://github.com/diegosouzapw/OmniRoute/pull/4756) — closes #4712, thanks @diegosouzapw). -- **Dashboard**: remove the dead, unconditional `useLiveRequests()` call from `HomePageClient.tsx` — it crashed the `/home` page in production builds with `ReferenceError: useLiveRequests is not defined` (#4759, #4745) and opened the live-dashboard WebSocket even when Provider Topology was hidden (#4596). The live feed remains owned by the settings-gated `HomeProviderTopologySection` ([#4761](https://github.com/diegosouzapw/OmniRoute/pull/4761) — thanks @diegosouzapw). -- **Providers dashboard**: dedupe provider nodes by id when adding a compatible provider (`upsertProviderNodeById`) so the same provider can no longer appear twice and no-op adds don't invalidate the compatible-provider memo ([#4768](https://github.com/diegosouzapw/OmniRoute/pull/4768) — closes #4746, thanks @diegosouzapw). -- **Storage VACUUM**: the scheduled VACUUM job now follows the Storage page settings (`scheduledVacuum` / `vacuumHour`) as the single source of truth; the legacy env-flag control path was removed ([#4726](https://github.com/diegosouzapw/OmniRoute/pull/4726) — thanks @rdself). -- **Tiers**: no-auth providers are now counted as free, and the free-tier filter returns an empty set instead of falling through to every provider ([#4753](https://github.com/diegosouzapw/OmniRoute/pull/4753) — thanks @megamen32 / @diegosouzapw). -- **Combos**: auto-promote `zeroLatencyOptimizationsEnabled` so legacy configs (pre-3.8.33 `fallbackCompressionMode="lite"`) round-trip cleanly on the first GUI edit ([#4774](https://github.com/diegosouzapw/OmniRoute/pull/4774) — thanks @KooshaPari / @diegosouzapw). +- **db-backups**:通过 `OMNIROUTE_DB_IMPORT_MAX_MB`(默认 100 MB,上限 4 GB)使数据库导入大小上限可配置,以便恢复大于 100 MB 的备份;错误消息现在会指向该环境变量和 VACUUM([#4757](https://github.com/diegosouzapw/OmniRoute/pull/4757) — 关闭 #4719,感谢 @diegosouzapw)。 +- **引导流程**:添加缺失的 `onboarding.tiers` 步骤标题翻译,使设置向导不再因 `MISSING_MESSAGE: onboarding.tiers` 而崩溃([#4755](https://github.com/diegosouzapw/OmniRoute/pull/4755) — 关闭 #4698,感谢 @diegosouzapw)。 +- **deepseek-web**:将 `role:"tool"` 结果折叠到单提示转录(`messagesToPrompt`)中,使工具输出能到达模型,而不是在后续轮次省略 `tools[]` 数组时被静默丢弃([#4756](https://github.com/diegosouzapw/OmniRoute/pull/4756) — 关闭 #4712,感谢 @diegosouzapw)。 +- **控制台**:从 `HomePageClient.tsx` 中移除无效且无条件的 `useLiveRequests()` 调用——它在生产构建中导致 `/home` 页面崩溃并报 `ReferenceError: useLiveRequests is not defined`(#4759, #4745),且即使隐藏了服务商拓扑也会打开 live WebSocket(#4596)。实时订阅现在由设置门控的 `HomeProviderTopologySection` 负责([#4761](https://github.com/diegosouzapw/OmniRoute/pull/4761) — 感谢 @diegosouzapw)。 +- **服务商控制台**:添加兼容服务商时按 id 去重(`upsertProviderNodeById`),使同一服务商不再出现两次,且空操作添加不会使兼容服务商备忘录失效([#4768](https://github.com/diegosouzapw/OmniRoute/pull/4768) — 关闭 #4746,感谢 @diegosouzapw)。 +- **存储 VACUUM**:定时 VACUUM 任务现在以存储页面设置(`scheduledVacuum` / `vacuumHour`)为唯一数据源;旧的环境变量控制路径已移除([#4726](https://github.com/diegosouzapw/OmniRoute/pull/4726) — 感谢 @rdself)。 +- **Tiers**:免认证服务商现在计为免费,免费层级过滤器返回空集合而非穿透到所有服务商([#4753](https://github.com/diegosouzapw/OmniRoute/pull/4753) — 感谢 @megamen32 / @diegosouzapw)。 +- **Combos**:自动提升 `zeroLatencyOptimizationsEnabled`,使旧配置(3.8.33 之前的 `fallbackCompressionMode="lite"`)在首次 GUI 编辑时能正常往返([#4774](https://github.com/diegosouzapw/OmniRoute/pull/4774) — 感谢 @KooshaPari / @diegosouzapw)。 -### 📝 Maintenance +### 📝 维护 -- **chatCore (#3501)**: continued the incremental decomposition of `executeProviderRequest` and the streaming/non-streaming hooks into pure leaf modules — top-level helpers + 6 pure leaves ([#4571](https://github.com/diegosouzapw/OmniRoute/pull/4571)), `resolveExecutorWithProxy` + `getExecutionCredentials` ([#4646](https://github.com/diegosouzapw/OmniRoute/pull/4646)), Claude message transforms ([#4708](https://github.com/diegosouzapw/OmniRoute/pull/4708)), `persistAttemptLogs` ([#4717](https://github.com/diegosouzapw/OmniRoute/pull/4717)), `stageTrace` + `compressionUsageReceipt` ([#4721](https://github.com/diegosouzapw/OmniRoute/pull/4721)), `prepareUpstreamBody` ([#4730](https://github.com/diegosouzapw/OmniRoute/pull/4730)), parse + non-streaming usage-stats ([#4762](https://github.com/diegosouzapw/OmniRoute/pull/4762)), `recordContextEditingTelemetryHook` ([#4779](https://github.com/diegosouzapw/OmniRoute/pull/4779)), `scheduleQuotaShareConsumption` ([#4780](https://github.com/diegosouzapw/OmniRoute/pull/4780)), `emitRequestGamificationEvent` ([#4776](https://github.com/diegosouzapw/OmniRoute/pull/4776)), `runPluginOnResponseHook` ([#4782](https://github.com/diegosouzapw/OmniRoute/pull/4782)), `scheduleStreamingQuotaShareConsumption` ([#4784](https://github.com/diegosouzapw/OmniRoute/pull/4784)), `recordCompressionCacheStats` ([#4792](https://github.com/diegosouzapw/OmniRoute/pull/4792)), `writeCavemanOutputAnalytics` ([#4794](https://github.com/diegosouzapw/OmniRoute/pull/4794)), `recordStreamingUsageStats` ([#4791](https://github.com/diegosouzapw/OmniRoute/pull/4791)), and `recordStreamingCost` ([#4790](https://github.com/diegosouzapw/OmniRoute/pull/4790)). (thanks @diegosouzapw) -- **Quality**: expand `check:release-green` to reproduce the full release-PR gate set locally ([#4758](https://github.com/diegosouzapw/OmniRoute/pull/4758) — thanks @diegosouzapw). -- **db**: re-export `compressionRunTelemetry` from `localDb` to satisfy the db-rules gate ([#4775](https://github.com/diegosouzapw/OmniRoute/pull/4775) — thanks @diegosouzapw). -- **Security docs**: add a canonical STRIDE-based threat model ([#4783](https://github.com/diegosouzapw/OmniRoute/pull/4783) — thanks @KooshaPari). -- **Tests**: add a smoke test for the home-client dashboard ([#4793](https://github.com/diegosouzapw/OmniRoute/pull/4793) — thanks @JxnLexn). -- **Docs**: credit **ponytail** and **OmniCompress** in the README inspiring-projects list and restore the `check:env-doc-sync` release-green by exempting the harness-only `OMNIROUTE_EVAL_CREDENTIALS` var ([#4799](https://github.com/diegosouzapw/OmniRoute/pull/4799) — thanks @diegosouzapw); declare the Phase 4 compression layers in the README + GUIDE ([#4801](https://github.com/diegosouzapw/OmniRoute/pull/4801) — thanks @diegosouzapw). -- **Quality**: trim `combo-config.test.ts` comments back under the file-size cap (follow-up to #4774) ([#4800](https://github.com/diegosouzapw/OmniRoute/pull/4800) — thanks @diegosouzapw). +- **chatCore (#3501)**:继续将 `executeProviderRequest` 和流式/非流式钩子增量分解为纯叶子模块——顶层辅助函数 + 6 个纯叶子([#4571](https://github.com/diegosouzapw/OmniRoute/pull/4571)),`resolveExecutorWithProxy` + `getExecutionCredentials`([#4646](https://github.com/diegosouzapw/OmniRoute/pull/4646)),Claude 消息转换([#4708](https://github.com/diegosouzapw/OmniRoute/pull/4708)),`persistAttemptLogs`([#4717](https://github.com/diegosouzapw/OmniRoute/pull/4717)),`stageTrace` + `compressionUsageReceipt`([#4721](https://github.com/diegosouzapw/OmniRoute/pull/4721)),`prepareUpstreamBody`([#4730](https://github.com/diegosouzapw/OmniRoute/pull/4730)),解析 + 非流式用量统计([#4762](https://github.com/diegosouzapw/OmniRoute/pull/4762)),`recordContextEditingTelemetryHook`([#4779](https://github.com/diegosouzapw/OmniRoute/pull/4779)),`scheduleQuotaShareConsumption`([#4780](https://github.com/diegosouzapw/OmniRoute/pull/4780)),`emitRequestGamificationEvent`([#4776](https://github.com/diegosouzapw/OmniRoute/pull/4776)),`runPluginOnResponseHook`([#4782](https://github.com/diegosouzapw/OmniRoute/pull/4782)),`scheduleStreamingQuotaShareConsumption`([#4784](https://github.com/diegosouzapw/OmniRoute/pull/4784)),`recordCompressionCacheStats`([#4792](https://github.com/diegosouzapw/OmniRoute/pull/4792)),`writeCavemanOutputAnalytics`([#4794](https://github.com/diegosouzapw/OmniRoute/pull/4794)),`recordStreamingUsageStats`([#4791](https://github.com/diegosouzapw/OmniRoute/pull/4791))和 `recordStreamingCost`([#4790](https://github.com/diegosouzapw/OmniRoute/pull/4790))。(感谢 @diegosouzapw) +- **质量**:扩展 `check:release-green` 以在本地复现完整的发布 PR 门控集合([#4758](https://github.com/diegosouzapw/OmniRoute/pull/4758) — 感谢 @diegosouzapw)。 +- **db**:从 `localDb` 重新导出 `compressionRunTelemetry` 以满足 db-rules 门控要求([#4775](https://github.com/diegosouzapw/OmniRoute/pull/4775) — 感谢 @diegosouzapw)。 +- **安全文档**:添加基于 STRIDE 的规范威胁模型([#4783](https://github.com/diegosouzapw/OmniRoute/pull/4783) — 感谢 @KooshaPari)。 +- **测试**:为 home-client 控制台添加冒烟测试([#4793](https://github.com/diegosouzapw/OmniRoute/pull/4793) — 感谢 @JxnLexn)。 +- **文档**:在 README 启发项目列表中注明 **ponytail** 和 **OmniCompress** 并修复 `check:env-doc-sync` release-green 检查([#4799](https://github.com/diegosouzapw/OmniRoute/pull/4799) — 感谢 @diegosouzapw);在 README + GUIDE 中声明第四阶段压缩层级([#4801](https://github.com/diegosouzapw/OmniRoute/pull/4801) — 感谢 @diegosouzapw)。 +- **质量**:修剪 `combo-config.test.ts` 注释以保持在文件大小上限以下(#4774 的后续工作)([#4800](https://github.com/diegosouzapw/OmniRoute/pull/4800) — 感谢 @diegosouzapw)。 --- ## [3.8.34] — 2026-06-23 -### ✨ New Features +### ✨ 新功能 -- **feat(executors): Microsoft 365 Copilot pure framing + connection helpers** — adds the request/response framing and connection helpers to support `m365.cloud.microsoft/chat` for individual M365 plans. ([#4696](https://github.com/diegosouzapw/OmniRoute/pull/4696) — thanks @skyzea1 / @diegosouzapw) -- **feat(compression): per-request `x-omniroute-compression` header (Phase 3)** — a request header now overrides the compression plan with the highest precedence (`request-header > routing > profile > auto-trigger > Default > off`), accepting `off` / `default` / `engine:` / ``. The response echoes `X-OmniRoute-Compression: ; source=`. ([#4645](https://github.com/diegosouzapw/OmniRoute/pull/4645) — thanks @diegosouzapw) -- **feat(audio): MiniMax T2A v2 TTS dispatch in `audioSpeech`** — adds MiniMax text-to-speech dispatch (port of upstream #1043). ([#4553](https://github.com/diegosouzapw/OmniRoute/pull/4553) — thanks @diegosouzapw) -- **feat(opencode): OpenCode Go DeepSeek reasoning variants** — registers the Go DeepSeek reasoning model variants. ([#4647](https://github.com/diegosouzapw/OmniRoute/pull/4647) — thanks @DevEstacion) -- **feat(quota): quota scraping for OpenCode Go and Ollama Cloud** — surfaces quota windows for the OpenCode Go and Ollama Cloud providers. ([#4642](https://github.com/diegosouzapw/OmniRoute/pull/4642) — thanks @JxnLexn) -- **feat(settings): expose stream recovery feature flags** — surfaces the stream-recovery toggles in settings. ([#4586](https://github.com/diegosouzapw/OmniRoute/pull/4586) — thanks @rdself) -- **feat(providers): optional model ID for custom API-key validation** — custom API-key connection tests can now specify the model ID used to validate the key. ([#4555](https://github.com/diegosouzapw/OmniRoute/pull/4555) — thanks @diegosouzapw) +- **feat(executors): Microsoft 365 Copilot 纯帧封装 + 连接辅助** — 新增请求/响应帧封装和连接辅助,支持 `m365.cloud.microsoft/chat` 用于个人 M365 计划。([#4696](https://github.com/diegosouzapw/OmniRoute/pull/4696) — 感谢 @skyzea1 / @diegosouzapw) +- **feat(compression): 按请求的 `x-omniroute-compression` 请求头(第 3 阶段)** — 请求头现在以最高优先级覆盖压缩方案(`请求头 > 路由 > profile > 自动触发 > 默认 > 关闭`),接受 `off` / `默认` / `引擎:` / ``。响应回显 `X-OmniRoute-Compression: ; source=`。([#4645](https://github.com/diegosouzapw/OmniRoute/pull/4645) — 感谢 @diegosouzapw) +- **feat(audio): MiniMax T2A v2 文本转语音调度接入 `audioSpeech`** — 新增 MiniMax 文本转语音调度(移植上游 #1043)。([#4553](https://github.com/diegosouzapw/OmniRoute/pull/4553) — 感谢 @diegosouzapw) +- **feat(opencode): OpenCode Go DeepSeek 推理变体** — 注册 Go DeepSeek 推理模型变体。([#4647](https://github.com/diegosouzapw/OmniRoute/pull/4647) — 感谢 @DevEstacion) +- **feat(quota): OpenCode Go 和 Ollama Cloud 配额抓取** — 展示 OpenCode Go 和 Ollama Cloud 服务商的配额窗口。([#4642](https://github.com/diegosouzapw/OmniRoute/pull/4642) — 感谢 @JxnLexn) +- **feat(settings): 暴露流式恢复功能开关** — 在设置中展示流式恢复开关。([#4586](https://github.com/diegosouzapw/OmniRoute/pull/4586) — 感谢 @rdself) +- **feat(providers): 自定义 API 密钥校验的可选模型 ID** — 自定义 API 密钥连接测试现在可指定用于校验密钥的模型 ID。([#4555](https://github.com/diegosouzapw/OmniRoute/pull/4555) — 感谢 @diegosouzapw) -### 🐛 Fixed +### 🐛 问题修复 -- **fix(db): scheduled cleanup actually runs + queries target the real tables (DB-bloat / OOM)** — `runAutoCleanup` was never scheduled, so retention cleanup never executed and tables (`compression_analytics`, `usage_history`, …) grew unbounded into multi-GB SQLite files driving high RSS. Worse, several cleanup queries referenced wrong table/column names (`call_logs.created_at`→`timestamp`, `compression_analytics.created_at`→`timestamp`, `mcp_audit_log`→`mcp_tool_audit`, `a2a_events`→`a2a_task_events`, `memory_entries`→`memories`), so even a manual run silently no-op'd or errored. Fixed the five queries to match the real schema, added `cleanupProxyLogs`, and wired a `startCleanupScheduler` (startup + every 6h, VACUUM after deletes) into `server-init` alongside the existing budget-reset and reasoning-cache jobs. ([#4691](https://github.com/diegosouzapw/OmniRoute/pull/4691), extracted from [#4428](https://github.com/diegosouzapw/OmniRoute/pull/4428) — thanks @oyi77 / @diegosouzapw) -- **fix(routing): include all noAuth models in auto-combos + add reka-flash + best-free template** — noAuth provider models are no longer skipped when building auto-combos, `reka-flash` is registered, and a `best-free` combo template is added. ([#4621](https://github.com/diegosouzapw/OmniRoute/pull/4621) — thanks @oyi77) -- **fix: noAuth provider validation + Kimi executor routing** — corrects noAuth provider membership checks and removes a mis-routed Kimi alias. (closes #4620) ([#4699](https://github.com/diegosouzapw/OmniRoute/pull/4699) — thanks @oyi77) -- **fix(executors): Firecrawl `web_fetch` 500 with `include_metadata=true`** — fixes a crash when Firecrawl web_fetch is invoked with metadata extraction enabled. ([#4692](https://github.com/diegosouzapw/OmniRoute/pull/4692) — thanks @ponkcore) -- **fix(proxy): apply `pipelining:0` + connections cap to the direct dispatcher** — same-provider concurrent requests no longer serialize behind a long/streaming request on the direct path. ([#4684](https://github.com/diegosouzapw/OmniRoute/pull/4684) — thanks @jeffer1312 / @diegosouzapw) -- **fix(telemetry): back off live-WS event forwarding when the sidecar is unreachable** — stops repeatedly attempting to connect to `LIVE_WS_PORT` when live monitoring is not configured. ([#4687](https://github.com/diegosouzapw/OmniRoute/pull/4687) — thanks @FikFikk / @diegosouzapw) -- **fix(api): serve `GET /v1/models/{model}` as JSON, not the HTML dashboard** — the per-model endpoint (IDs with slashes via a catch-all route) now returns JSON, unbreaking Claude Code. ([#4677](https://github.com/diegosouzapw/OmniRoute/pull/4677) — thanks @papajo / @diegosouzapw) -- **fix(executors): robust deepseek-web tool-call parsing and agentic context retention** — hardens DeepSeek-web tool-call parsing and preserves agentic context across turns. ([#4644](https://github.com/diegosouzapw/OmniRoute/pull/4644) — thanks @BugsBag) -- **fix(cli): authenticate `omniroute logs` and honor the active context** — the `logs` command now authenticates and respects the active context. ([#4638](https://github.com/diegosouzapw/OmniRoute/pull/4638) — thanks @Rahulsharma0810) -- **fix(stream): estimate input tokens when upstream reports `prompt_tokens=0`** — input token usage is estimated when the upstream omits it. ([#4615](https://github.com/diegosouzapw/OmniRoute/pull/4615) — thanks @adivekar-utexas) -- **fix(plugin): auto-prefix providerId with `opencode-` for OpenCode 1.17.8+ native gate** — adapts provider IDs to the OpenCode 1.17.8+ native provider gate. ([#4527](https://github.com/diegosouzapw/OmniRoute/pull/4527) — thanks @herjarsa) -- **fix(catalog): shorten no-thinking gateway prefix to `no-think/`** — renames the no-thinking gateway prefix. ([#4525](https://github.com/diegosouzapw/OmniRoute/pull/4525) — thanks @Rahulsharma0810) -- **fix(models): unknown max output limits no longer default to 8192** — models without synced/registry/static `maxOutputTokens` resolve the limit as unknown instead of a generic 8192 cap; clamping/injection only happens when a real cap is known. ([#4584](https://github.com/diegosouzapw/OmniRoute/pull/4584) — thanks @rdself) -- **fix(resilience): respect upstream retry-hint toggle** — honors the configured toggle for upstream retry hints. ([#4585](https://github.com/diegosouzapw/OmniRoute/pull/4585) — thanks @rdself) -- **fix(providers): show revealed connection API keys** — fixes revealing stored connection API keys in the UI. ([#4583](https://github.com/diegosouzapw/OmniRoute/pull/4583) — thanks @rdself) -- **fix(logs): make active-request stale sweep configurable** — exposes the stale-request sweep interval as a setting. ([#4599](https://github.com/diegosouzapw/OmniRoute/pull/4599) — thanks @rdself) -- **fix(resilience): retain provider cooldowns for the configured max window** — cooldowns persist for the configured maximum window. ([#4588](https://github.com/diegosouzapw/OmniRoute/pull/4588) — thanks @KooshaPari) -- **fix(resilience): reject invalid provider cooldown bounds** — validates cooldown bound configuration. ([#4589](https://github.com/diegosouzapw/OmniRoute/pull/4589) — thanks @KooshaPari) -- **fix(combo): preserve production combo metrics on shadow eviction** — shadow eviction no longer drops production combo metrics. ([#4590](https://github.com/diegosouzapw/OmniRoute/pull/4590) — thanks @KooshaPari) -- **fix(combo): exclude exhausted connections from auto scoring** — exhausted connections are no longer scored as auto-combo candidates. ([#4592](https://github.com/diegosouzapw/OmniRoute/pull/4592) — thanks @KooshaPari) -- **fix(relay): apply IP rate limit to the Bifrost sidecar** — extends IP rate limiting to the Bifrost relay sidecar. ([#4593](https://github.com/diegosouzapw/OmniRoute/pull/4593) — thanks @KooshaPari) -- **fix(bifrost): finalize SSE relay usage after stream** — finalizes relay usage accounting once the SSE stream completes. ([#4612](https://github.com/diegosouzapw/OmniRoute/pull/4612) — thanks @KooshaPari) -- **fix(quota): expose Bailian quota windows** — surfaces Bailian provider quota windows. ([#4610](https://github.com/diegosouzapw/OmniRoute/pull/4610) — thanks @KooshaPari) -- **fix(dashboard): gate home topology live-WS networking behind widget visibility** — the home dashboard no longer starts topology polling / live sockets when topology is hidden. ([#4618](https://github.com/diegosouzapw/OmniRoute/pull/4618), [#4606](https://github.com/diegosouzapw/OmniRoute/pull/4606) — thanks @KooshaPari) -- **fix(dashboard): isolate the quota widget refresh clock** — the quota widget refresh no longer drives unrelated re-renders. ([#4611](https://github.com/diegosouzapw/OmniRoute/pull/4611) — thanks @KooshaPari) -- **fix(dashboard): memoize compatible provider groups** — avoids recomputing compatible provider groups on every render. ([#4613](https://github.com/diegosouzapw/OmniRoute/pull/4613) — thanks @KooshaPari) -- **fix(cli): align `omniroute` data dir and env loading with the runtime** — the CLI's data-dir/env loading no longer drifts from the server runtime configuration. ([#4619](https://github.com/diegosouzapw/OmniRoute/pull/4619), [#4607](https://github.com/diegosouzapw/OmniRoute/pull/4607) — thanks @KooshaPari) -- **fix(api/settings): prevent cached `/api/settings` responses** — disables caching on the settings endpoint (port from 9router#951). ([#4566](https://github.com/diegosouzapw/OmniRoute/pull/4566) — thanks @diegosouzapw) -- **fix(executors): strip temperature for the GitHub Copilot gpt-5.4 family** — removes the unsupported `temperature` param for Copilot gpt-5.4 models (port from 9router#612). ([#4564](https://github.com/diegosouzapw/OmniRoute/pull/4564) — thanks @diegosouzapw) -- **fix(dashboard): keep play_arrow spinning on provider "Test All" buttons** — fixes the spinner state on the provider test buttons (port from 9router#715). ([#4563](https://github.com/diegosouzapw/OmniRoute/pull/4563) — thanks @diegosouzapw) -- **fix(dashboard): surface manual config CTA when Open Claw CLI auto-detect fails** — shows a manual-config call-to-action on the Open Claw CLI card when auto-detection fails. ([#4562](https://github.com/diegosouzapw/OmniRoute/pull/4562) — thanks @diegosouzapw) -- **fix(oauth): update Qwen OAuth URLs from `chat.qwen.ai` to `qwen.ai`** — refreshes the Qwen OAuth endpoints (port of decolua/9router#683). ([#4561](https://github.com/diegosouzapw/OmniRoute/pull/4561) — thanks @diegosouzapw) +- **fix(db): 定时清理真正运行 + 查询指向正确的表(数据库膨胀 / OOM)** — `runAutoCleanup` 从未被调度,导致保留清理从未执行,表(`compression_analytics`、`usage_history` 等)无限增长成数 GB 的 SQLite 文件,推高 RSS。更糟糕的是,多个清理查询引用了错误的表名/列名(`call_logs.created_at`→`timestamp`、`compression_analytics.created_at`→`timestamp`、`mcp_audit_log`→`mcp_tool_audit`、`a2a_events`→`a2a_task_events`、`memory_entries`→`memories`),因此即使手动运行也静默无操作或报错。修复了五条查询以匹配真实 schema,新增 `cleanupProxyLogs`,并将 `startCleanupScheduler`(启动时 + 每 6 小时,删除后执行 VACUUM)接入 `server-init`,与现有的预算重置和推理缓存任务并列运行。([#4691](https://github.com/diegosouzapw/OmniRoute/pull/4691),提取自 [#4428](https://github.com/diegosouzapw/OmniRoute/pull/4428) — 感谢 @oyi77 / @diegosouzapw) +- **fix(routing): 自动 Combo 纳入所有 noAuth 模型 + 新增 reka-flash + 最佳免费模板** — 构建自动 Combo 时不再跳过 noAuth 服务商模型,注册 `reka-flash`,并新增 `best-free` Combo 模板。([#4621](https://github.com/diegosouzapw/OmniRoute/pull/4621) — 感谢 @oyi77) +- **fix: noAuth 服务商校验 + Kimi 执行器路由** — 修正 noAuth 服务商成员检查,移除错误路由的 Kimi 别名。(关闭 #4620)([#4699](https://github.com/diegosouzapw/OmniRoute/pull/4699) — 感谢 @oyi77) +- **fix(executors): Firecrawl `web_fetch` 携带 `include_metadata=true` 时返回 500** — 修复 Firecrawl web_fetch 在启用元数据提取时崩溃的问题。([#4692](https://github.com/diegosouzapw/OmniRoute/pull/4692) — 感谢 @ponkcore) +- **fix(proxy): 对直连调度器应用 `pipelining:0` + 连接数上限** — 同一服务商的并发请求不再因一个长时/流式传输请求而在直连路径上排队等待。([#4684](https://github.com/diegosouzapw/OmniRoute/pull/4684) — 感谢 @jeffer1312 / @diegosouzapw) +- **fix(telemetry): sidecar 不可达时退避实时 WebSocket 事件转发** — 当未配置实时监控时,停止反复尝试连接 `LIVE_WS_PORT`。([#4687](https://github.com/diegosouzapw/OmniRoute/pull/4687) — 感谢 @FikFikk / @diegosouzapw) +- **fix(api): `GET /v1/models/{model}` 返回 JSON 而非 HTML 仪表盘** — 按模型端点(通过通配路由支持含斜杠的 ID)现在返回 JSON,修复 Claude Code 兼容性问题。([#4677](https://github.com/diegosouzapw/OmniRoute/pull/4677) — 感谢 @papajo / @diegosouzapw) +- **fix(executors): 增强 deepseek-web 工具调用解析和代理上下文保留** — 加固 DeepSeek-web 工具调用解析,并在多轮对话中保留代理上下文。([#4644](https://github.com/diegosouzapw/OmniRoute/pull/4644) — 感谢 @BugsBag) +- **fix(cli): `omniroute logs` 认证并遵循当前上下文** — `logs` 命令现在进行认证并遵循当前上下文。([#4638](https://github.com/diegosouzapw/OmniRoute/pull/4638) — 感谢 @Rahulsharma0810) +- **fix(stream): 上游报告 `prompt_tokens=0` 时估算输入令牌** — 当上游省略输入令牌用量时进行估算。([#4615](https://github.com/diegosouzapw/OmniRoute/pull/4615) — 感谢 @adivekar-utexas) +- **fix(plugin): 为 OpenCode 1.17.8+ 原生网关自动添加 `opencode-` 前缀** — 适配服务商 ID 以对接 OpenCode 1.17.8+ 原生服务商网关。([#4527](https://github.com/diegosouzapw/OmniRoute/pull/4527) — 感谢 @herjarsa) +- **fix(catalog): 将无思考网关前缀缩短为 `no-think/`** — 重命名无思考网关前缀。([#4525](https://github.com/diegosouzapw/OmniRoute/pull/4525) — 感谢 @Rahulsharma0810) +- **fix(models): 未知最大输出限制不再默认设为 8192** — 没有同步/注册表/静态 `maxOutputTokens` 的模型将限制解析为未知,而非通用的 8192 上限;仅当已知真实上限时才执行钳制/注入。([#4584](https://github.com/diegosouzapw/OmniRoute/pull/4584) — 感谢 @rdself) +- **fix(resilience): 遵循上游重试提示开关** — 遵循配置的上游重试提示开关。([#4585](https://github.com/diegosouzapw/OmniRoute/pull/4585) — 感谢 @rdself) +- **fix(providers): 显示已保存连接的 API 密钥** — 修复在 UI 中显示已保存连接 API 密钥的功能。([#4583](https://github.com/diegosouzapw/OmniRoute/pull/4583) — 感谢 @rdself) +- **fix(logs): 使活跃请求过期清理可配置** — 将过期请求清理间隔暴露为设置项。([#4599](https://github.com/diegosouzapw/OmniRoute/pull/4599) — 感谢 @rdself) +- **fix(resilience): 在配置的最大窗口内保留服务商冷却状态** — 冷却状态在配置的最大窗口内持久保留。([#4588](https://github.com/diegosouzapw/OmniRoute/pull/4588) — 感谢 @KooshaPari) +- **fix(resilience): 拒绝无效的服务商冷却边界值** — 校验冷却边界配置。([#4589](https://github.com/diegosouzapw/OmniRoute/pull/4589) — 感谢 @KooshaPari) +- **fix(combo): 影子淘汰时保留生产 Combo 指标** — 影子淘汰不再丢弃生产 Combo 指标。([#4590](https://github.com/diegosouzapw/OmniRoute/pull/4590) — 感谢 @KooshaPari) +- **fix(combo): 自动评分排除已耗尽的连接** — 已耗尽的连接不再被评分为自动 Combo 候选。([#4592](https://github.com/diegosouzapw/OmniRoute/pull/4592) — 感谢 @KooshaPari) +- **fix(relay): 对 Bifrost sidecar 应用 IP 速率限制** — 将 IP 速率限制扩展到 Bifrost 中继 sidecar。([#4593](https://github.com/diegosouzapw/OmniRoute/pull/4593) — 感谢 @KooshaPari) +- **fix(bifrost): 流结束后完成 SSE 中继用量统计** — SSE 流完成后完成中继用量统计。([#4612](https://github.com/diegosouzapw/OmniRoute/pull/4612) — 感谢 @KooshaPari) +- **fix(quota): 展示 Bailian 配额窗口** — 展示 Bailian 服务商配额窗口。([#4610](https://github.com/diegosouzapw/OmniRoute/pull/4610) — 感谢 @KooshaPari) +- **fix(dashboard): 首页拓扑实时 WebSocket 网络受小部件可见性控制** — 首页控制台在拓扑隐藏时不再启动拓扑轮询/实时 socket 连接。([#4618](https://github.com/diegosouzapw/OmniRoute/pull/4618), [#4606](https://github.com/diegosouzapw/OmniRoute/pull/4606) — 感谢 @KooshaPari) +- **fix(dashboard): 隔离配额小部件刷新时钟** — 配额小部件刷新不再触发无关的重新渲染。([#4611](https://github.com/diegosouzapw/OmniRoute/pull/4611) — 感谢 @KooshaPari) +- **fix(dashboard): 缓存兼容服务商分组** — 避免每次渲染时重新计算兼容服务商分组。([#4613](https://github.com/diegosouzapw/OmniRoute/pull/4613) — 感谢 @KooshaPari) +- **fix(cli): `omniroute` 数据目录和环境加载与运行时对齐** — CLI 的数据目录/环境加载不再偏离服务器运行时配置。([#4619](https://github.com/diegosouzapw/OmniRoute/pull/4619), [#4607](https://github.com/diegosouzapw/OmniRoute/pull/4607) — 感谢 @KooshaPari) +- **fix(api/settings): 防止 `/api/settings` 响应被缓存** — 禁用设置端点的缓存(移植自 9router#951)。([#4566](https://github.com/diegosouzapw/OmniRoute/pull/4566) — 感谢 @diegosouzapw) +- **fix(executors): GitHub Copilot gpt-5.4 系列移除 temperature 参数** — 移除 Copilot gpt-5.4 模型不支持的 `temperature` 参数(移植自 9router#612)。([#4564](https://github.com/diegosouzapw/OmniRoute/pull/4564) — 感谢 @diegosouzapw) +- **fix(dashboard): 服务商"全部测试"按钮保持 play_arrow 旋转动画** — 修复服务商测试按钮的旋转状态(移植自 9router#715)。([#4563](https://github.com/diegosouzapw/OmniRoute/pull/4563) — 感谢 @diegosouzapw) +- **fix(dashboard): Open Claw CLI 自动检测失败时显示手动配置引导** — 在 Open Claw CLI 卡片上自动检测失败时显示手动配置引导按钮。([#4562](https://github.com/diegosouzapw/OmniRoute/pull/4562) — 感谢 @diegosouzapw) +- **fix(oauth): 更新 Qwen OAuth URL 从 `chat.qwen.ai` 到 `qwen.ai`** — 刷新 Qwen OAuth 端点(移植自 decolua/9router#683)。([#4561](https://github.com/diegosouzapw/OmniRoute/pull/4561) — 感谢 @diegosouzapw) -### 📝 Maintenance +### 📝 维护 -- **refactor(imageGeneration): extract 8 provider families to co-located files** — splits the image-generation module into eight co-located per-provider files with no behavioral change. ([#4609](https://github.com/diegosouzapw/OmniRoute/pull/4609) — thanks @KooshaPari) -- **deps: bump production + development groups; migrate js-yaml to v5 (ESM)** — dependency bumps plus a `js-yaml` v4→v5 migration to the ESM-only namespace import. ([#4697](https://github.com/diegosouzapw/OmniRoute/pull/4697) — thanks @diegosouzapw) -- **chore(quality): release-green pre-flight validator + nightly signal** — new `npm run check:release-green` (`scripts/quality/validate-release-green.mjs`) reproduces the release-equivalent validation (full unit + vitest + ratchets + typecheck + lint, optional `--with-build` package-artifact) against the current working tree and classifies each red as **HARD** (real defect) vs **DRIFT** (ratchet, rebaselined at release) — purely diagnostic, never blocking contributors. A new `nightly-release-green` workflow runs it on the active release branch and opens/updates a tracking issue on hard failures. Closes the gap where the full gate (`ci.yml`) only ran on the release PR, so reds accrued silently on `release/**` and surfaced in layers at release time. ([#4622](https://github.com/diegosouzapw/OmniRoute/pull/4622) — thanks @diegosouzapw) -- **chore(quality): reconcile file-size baseline for #4644 (`deepseek-web.ts` 1117→1125)** — rebaselines the file-size gate after the deepseek-web hardening. ([#4695](https://github.com/diegosouzapw/OmniRoute/pull/4695) — thanks @diegosouzapw) +- **refactor(imageGeneration): 提取 8 个服务商系列到并列文件** — 将图像生成模块拆分为八个并列的按服务商文件,无行为变更。([#4609](https://github.com/diegosouzapw/OmniRoute/pull/4609) — 感谢 @KooshaPari) +- **deps: 升级生产 + 开发依赖组;迁移 js-yaml 到 v5 (ESM)** — 依赖升级,外加 `js-yaml` v4→v5 迁移到纯 ESM 命名空间导入。([#4697](https://github.com/diegosouzapw/OmniRoute/pull/4697) — 感谢 @diegosouzapw) +- **chore(quality): 发布绿灯预检校验器 + 夜间信号** — 新增 `npm run check:release-green`(`scripts/quality/validate-release-green.mjs`),针对当前工作树重现等效发布的校验(完整单元测试 + vitest + ratchets + typecheck + lint,可选 `--with-build` 构建产物),并将每个红色项分类为 **HARD**(真实缺陷)或 **DRIFT**(ratchet 漂移,发布时重新基线化)——纯诊断性质,永不阻止贡献者。新增 `nightly-release-green` 工作流,在活跃的发布分支上运行该校验,并在硬故障时打开/更新跟踪 issue。填补了完整门禁(`ci.yml`)仅在发布 PR 上运行、导致 `release/**` 上的红色项静默累积并在发布时分批暴露的空白。([#4622](https://github.com/diegosouzapw/OmniRoute/pull/4622) — 感谢 @diegosouzapw) +- **chore(quality): 为 #4644 调整文件大小基线(`deepseek-web.ts` 1117→1125)** — deepseek-web 加固后重新基线化文件大小门禁。([#4695](https://github.com/diegosouzapw/OmniRoute/pull/4695) — 感谢 @diegosouzapw) --- @@ -1053,344 +1071,344 @@ _See English CHANGELOG for v3.8.32 details._ ## [3.8.31] — 2026-06-20 -### ✨ New Features +### ✨ 新功能 -- **perf(dashboard): combos UI leaf-split, Next.js config tuning, 1-click Redis & Bifrost sidecar** — delivers four of the five performance/UX tracks from the #3932 thread: the combos dashboard page is split into focused leaf components (smaller bundles, faster reloads), `next.config` is tuned for the standalone build, Redis can be provisioned in one click, and a Bifrost sidecar option is wired in. (The fifth track — chatLogHelpers extraction — was already covered upstream and dropped.) ([#4381](https://github.com/diegosouzapw/OmniRoute/pull/4381) — thanks @KooshaPari) +- **perf(dashboard): 组合 UI 叶子组件拆分、Next.js 配置调优、一键 Redis 和 Bifrost sidecar** — 交付了 #3932 讨论串中五个性能/UX 轨道中的四个:组合 控制台 页面被拆分为专注的叶子组件(更小的打包体积、更快的重载速度),`next.配置` 针对独立构建进行了调优,Redis 可一键配置,Bifrost sidecar 选项也已接入。(第五个轨道 — chatLogHelpers 提取 — 已在上游完成,因此移除。)([#4381](https://github.com/diegosouzapw/OmniRoute/pull/4381) — 感谢 @KooshaPari) -### 🐛 Fixed +### 🐛 问题修复 -- **fix(embeddings): NVIDIA NIM asymmetric embedding models inject the required `input_type`** — NVIDIA NIM asymmetric embedders (e.g. `nvidia/nv-embedqa-e5-v5`) reject requests without an `input_type` parameter with `400 "'input_type' parameter is required"`, but OmniRoute only forwarded `input_type` when the client supplied it — so callers (and OpenAI-style SDKs that don't emit the field) got a hard failure. The embedding registry now carries a model-level default (`input_type: "query"`) for the asymmetric NVIDIA model, and the embeddings handler injects a model's default params into the upstream body **only** when the client didn't already send them — a client-supplied `input_type` (e.g. `"passage"`) is respected unchanged, and symmetric models that carry no default are unaffected. ([#4341](https://github.com/diegosouzapw/OmniRoute/pull/4341) — thanks @hydraromania) -- **fix(api): migrate the deprecated Codex `[features].codex_hooks` flag to `[features].hooks`** — Codex renamed the `codex_hooks` feature flag to `hooks`; recent Codex CLI versions ignore the old key and print a deprecation notice. When OmniRoute rewrites an existing `~/.codex/config.toml` (configuring/resetting the Codex provider) it now carries the user's intent forward by renaming `[features].codex_hooks` → `[features].hooks` (preserving its value, never clobbering an already-present `hooks`) and dropping the deprecated key. No-op when the flag is absent. ([#4342](https://github.com/diegosouzapw/OmniRoute/pull/4342) — thanks @Bian-Sh) -- **fix(translator): same-format response path no longer leaks a `data: null` SSE event** — the streaming response translator's same-format fast path returned `[chunk]` unconditionally, so the end-of-stream null/flush signal (`chunk === null`) propagated as a literal `[null]`. Downstream this surfaced as an empty `data: null` SSE event between chunks and crashed strict clients (e.g. Factory Droid BYOK on `/v1/responses`). The fast path now drops the null flush (returns `[]`) while still passing real chunks through unchanged. ([#4344](https://github.com/diegosouzapw/OmniRoute/pull/4344) — thanks @thaitryhand) -- **fix(translator): strip client-only assistant echo fields on the OpenAI target path (Mistral 422)** — strict OpenAI-compatible upstreams (e.g. `mistral/codestral-latest`) reject client-only assistant "echo" fields sent back as input history with `422 extra_forbidden` (the report hit `messages[].assistant.reasoning_content` via Codex `/responses`). Only `reasoning_content` was being stripped on the OpenAI target path; the sibling echo fields `reasoning`, `refusal`, `annotations` and `cache_control` leaked through and tripped the 422. They are now all dropped on the non-reasoner OpenAI target path. `audio` is deliberately preserved (OpenAI audio models reference a prior assistant audio response by id on multi-turn; Mistral never emits audio, so nothing is lost there). ([#4350](https://github.com/diegosouzapw/OmniRoute/pull/4350) — thanks @xxy9468615) -- **fix(translator): accept AI SDK-style `{ type: "image", image: "data:…" }` content parts** — several OpenAI-input translators only recognized images shaped as `image_url.url` (or an object with `.source`/`.url`), so an AI SDK-style part where `image` is a bare data-URL **string** was silently dropped before reaching a vision provider (OpenCode is one affected client; the gap is generic). The OpenAI→Claude, OpenAI→Kiro and OpenAI→Gemini/Antigravity translators now parse a string `image` data URL into each provider's native image shape (Claude `{source:{type:"base64"}}`, Kiro `images[].source.bytes`, Gemini `inlineData`). ([#4345](https://github.com/diegosouzapw/OmniRoute/pull/4345) — thanks @mugnimaestra) -- **fix(translator): Gemini accepts HTTP/HTTPS image URLs instead of silently dropping them** — the OpenAI→Gemini request helper (`convertOpenAIContentToParts`) discarded remote `image_url` parts (emitting only a `console.warn`) because Gemini's `inlineData` needs base64 and the synchronous helper can't fetch+encode upstream. It now uses Gemini's native `fileData: { fileUri }` part for HTTP/HTTPS URLs (the model fetches the asset itself), so vision requests carrying a URL — not a `data:` URI — reach Gemini intact. ([#4373](https://github.com/diegosouzapw/OmniRoute/pull/4373) — ported from 9router#344, thanks @diegosouzapw) -- **fix(executors): strip `stream_options` for qwen non-streaming / thinking Claude-Code requests** — Claude-Code-compatible providers force the executor-level `stream` flag on while the outgoing body keeps the caller's original `stream: false`, so `DefaultExecutor.transformRequest` injected `stream_options: { include_usage: true }` onto a body that still said `stream: false`, and qwen rejected it with `400 "'stream_options' only set this when you set stream: true"`. The executor now strips `stream_options` whenever the body's effective `stream` is false. ([#4374](https://github.com/diegosouzapw/OmniRoute/pull/4374) — ported from 9router#663, thanks @anuragg-saxenaa / @diegosouzapw) -- **fix(executors): don't inject `thinking` when `tool_choice` forces a tool (native Claude)** — the Claude-Code wire-image emulation injects `thinking: { type: "adaptive" }` for non-Haiku Claude models, but Anthropic rejects `thinking` when `tool_choice` forces a specific tool (`{type:"any"|"tool"}`) with `400 "Thinking may not be enabled when tool_choice forces tool use."`. Any Opus/Sonnet call that pins a tool (e.g. Claude Code's `message_user`, or agent harnesses that force a tool) hit a hard 400; the injection is now suppressed when `tool_choice` forces a tool. ([#4389](https://github.com/diegosouzapw/OmniRoute/pull/4389) — thanks @NomenAK) -- **fix(codex): request reasoning summaries on Codex Responses requests** — Codex/OpenAI Responses can return reasoning-token accounting and empty reasoning items unless visible reasoning summaries are requested, so Codex CLI / pi.dev paths missed visible thinking text. OmniRoute now requests `reasoning.summary: "auto"` (and includes `reasoning.encrypted_content`) when reasoning is enabled — preserving an explicit client `reasoning.summary` and existing `include` entries, and skipping it for `reasoning.effort: "none"`. ([#4359](https://github.com/diegosouzapw/OmniRoute/pull/4359) — thanks @xz-dev) -- **fix(sse): default the combo per-target timeout to 120s for fast failover** — a combo's per-target timeout inherited the full `FETCH_TIMEOUT_MS` (600s default) when the combo didn't set `targetTimeoutMs`, so a single hung/slow target (e.g. an openai-compatible upstream returning 524/504) could stall the **whole** combo for up to 10 minutes before failing over. A new `DEFAULT_COMBO_TARGET_TIMEOUT_MS = 120_000` is used as the default-when-unset in `resolveComboTargetTimeoutMs` (backward-compatible 3rd arg, wired in `phaseComboSetup`); an explicit ceiling/opt-out is preserved. ([#4365](https://github.com/diegosouzapw/OmniRoute/pull/4365) — thanks @diegosouzapw) -- **fix(cli): Tailscale login honors `TAILSCALE_AUTHKEY` for non-interactive sign-in** — `startTailscaleLogin` built `tailscale up` without ever reading `process.env.TAILSCALE_AUTHKEY`, so on a pre-authenticated / headless daemon the login waited for an interactive auth URL and timed out (~15s). When `TAILSCALE_AUTHKEY` is set it is now passed via `--auth-key=` (as a spawn argv element — no shell interpolation) so the daemon authenticates non-interactively; when unset, behavior is unchanged. ([#4343](https://github.com/diegosouzapw/OmniRoute/pull/4343) — thanks @ipeterpetrus) -- **fix(dashboard): OAuth modal shows the real error on a non-JSON server response** — the OAuth connect/reauth modal called `await res.json()` unconditionally, so when a build/OAuth endpoint returned a plain-text error (e.g. a `500 Internal Server Error` page) the modal threw `Unexpected token 'I'…` and hid the real failure. Two shared helpers (`parseResponseBody` / `getErrorMessage` in `src/shared/utils/api.ts`) now read the body safely (JSON when it is JSON, raw text otherwise) and surface a clean message either way; all modal fetch sites use them. ([#4351](https://github.com/diegosouzapw/OmniRoute/pull/4351) — thanks @DNNYF) -- **fix(dashboard): a disabled connection's last error is now visible** — the provider card's error badge counts a disabled connection (`isActive === false`) that has an error (its effective status is still error/expired/unavailable), but the connection row hid the `lastError` text for disabled rows — so the operator saw the error count without being able to see what failed. The row now shows the error text whenever there is one, regardless of the active toggle. ([#4352](https://github.com/diegosouzapw/OmniRoute/pull/4352) — thanks @ntdung6868) -- **fix(providers): the "Test Connection One-by-One" OAuth probe can no longer hang the queue forever** — the OAuth connection-test path called bare `fetch(url, { method, headers })` with no `AbortController`/signal/timeout, so when a provider's probe endpoint accepted the socket but never responded, the awaited fetch never settled and the one-by-one test queue stalled indefinitely (the API-key path was already bounded via `validateProviderApiKey`'s `timeoutMs`). Both the initial probe and the post-refresh retry are now bounded with `AbortSignal.timeout(30s)` — matching the API-key path's 30s budget — and a timed-out probe resolves as a failure with a clear `Test timed out after 30s` message in the same shape as every other test error. ([#4347](https://github.com/diegosouzapw/OmniRoute/pull/4347) — thanks @ntdung6868) -- **fix(providers): a deactivated account is labeled distinctly from a revoked token** — a Codex connection whose OAuth refresh is fully healthy but whose ChatGPT account has been deactivated by the provider gets a `401` from the upstream API. The connection test labeled that the same as a bad credential (`Token invalid or revoked` → `upstream_auth_error`), so the operator couldn't tell a deactivated account from a revoked token. The test now reads the `401`/`403` body and, when it indicates account deactivation, classifies it as `account_deactivated` — which the dashboard already renders as "Account Deactivated". A plain auth `401` is unchanged. ([#4353](https://github.com/diegosouzapw/OmniRoute/pull/4353) — thanks @ntdung6868) -- **fix(db): cascade-delete orphaned model aliases when a provider is removed** — deleting a custom provider removed its connections and node but left behind the imported model-alias rows (stored as `key=`, `value="/"`). Those stale aliases then blocked re-importing the same provider — the import dedup treated them as "already exists", so no new models appeared. A new `deleteModelAliasesForProvider(providerId)` DB helper drops every alias whose stored value begins with `/` (leaving other providers and user-defined settings aliases untouched), and the provider-node DELETE handler now calls it after removing the connections and node, so a fresh import is unblocked. ([#4348](https://github.com/diegosouzapw/OmniRoute/pull/4348) — thanks @nguyenvanhuy0612) -- **fix(api): persist `max_input_tokens` / `max_output_tokens` when adding a custom model** — `POST /api/provider-models` silently dropped the per-model token limits set in the "add custom model" form: the handler destructured the rest of the body but never read `max_input_tokens` / `max_output_tokens`, and `addCustomModel()` had no parameter for them, so the values were thrown away on write. The DB layer (`inputTokenLimit` / `outputTokenLimit`) and the `/v1/models` catalog already round-trip these fields — only the write path was missing. The validation schema now accepts the two optional limits, the handler forwards them, and `addCustomModel()` persists them so a custom model's context/output window survives into the catalog. ([#4349](https://github.com/diegosouzapw/OmniRoute/pull/4349) — thanks @codename-zen) -- **fix(plugin): the OpenCode static-catalog plugin prefixes combo/raw model keys with the provider id** — OpenCode's static-catalog reader misdetected the `omniroute` provider: combo keys emitted as `combo/MASTER` were parsed as provider `combo` ("No credentials for provider: omniroute"), while a bare-`MASTER` form was misread as a model with no resolvable provider, and mixed `omniroute/MASTER` + bare-raw keys were rejected by OpenCode's schema. The plugin now emits every combo and raw model key prefixed with the `omniroute` provider id, emits the provider id explicitly, and drops the legacy `combo/` prefix — so the static-catalog reader detects the provider and the auth loader returns the right credentials (the catalog-fetch timeout was also raised so a cold-start server doesn't publish an empty stub). ([#4384](https://github.com/diegosouzapw/OmniRoute/pull/4384) — thanks @herjarsa) +- **fix(embeddings): NVIDIA NIM 非对称嵌入模型注入必需的 `input_type`** — NVIDIA NIM 非对称嵌入模型(如 `nvidia/nv-embedqa-e5-v5`)会拒绝没有 `input_type` 参数的请求,返回 `400 "'input_type' parameter is required"`,但 OmniRoute 仅在客户端提供 `input_type` 时才转发 — 因此调用方(以及不发送该字段的 OpenAI 风格 SDK)会遭遇硬性失败。嵌入注册表现在为 NVIDIA 非对���模型携带模型级默认值(`input_type: "query"`),嵌入处理器仅在客户端未发送时才会将模型默认参数注入上游请求体 — 客户端提供的 `input_type`(如 `"passage"`)将原样保留,而没有默认值的对称模型不受影响。([#4341](https://github.com/diegosouzapw/OmniRoute/pull/4341) — 感谢 @hydraromania) +- **fix(api): 将已弃用的 Codex `[features].codex_hooks` 标志迁移到 `[features].hooks`** — Codex 将 `codex_hooks` 功能标志重命名为 `hooks`;最近的 Codex CLI 版本会忽略旧键并打印弃用通知。当 OmniRoute 重写现有的 `~/.codex/config.toml`(配置/重置 Codex 服务商)时,现在会通过重命名 `[features].codex_hooks` → `[features].hooks`(保留其值,绝不覆盖已存在的 `hooks`)并丢弃已弃用的键来保留用户意图。当该标志不存在时无操作。([#4342](https://github.com/diegosouzapw/OmniRoute/pull/4342) — 感谢 @Bian-Sh) +- **fix(translator): 同格式响应路径不再泄露 `data: null` SSE 事件** — 流式传输 响应翻译器的同格式快速路径无条件返回 `[chunk]`,因此流结束时的 null/刷新信号(`chunk === null`)被原样传播为 `[null]`。在下游表现为 chunk 之间的空 `data: null` SSE 事件,导致严格客户端(如 Factory Droid BYOK 在 `/v1/responses` 上)崩溃。快速路径现在丢弃 null 刷新(返回 `[]`),同时仍原样传递真实 chunk。([#4344](https://github.com/diegosouzapw/OmniRoute/pull/4344) — 感谢 @thaitryhand) +- **fix(translator): 在 OpenAI 目标路径上剥离仅客户端使用的助手回显字段(修复 Mistral 422)** — 严格的 OpenAI 兼容上游(如 `mistral/codestral-latest`)会拒绝作为输入历史发送回去的仅客户端使用的助手"回显"字段,返回 `422 extra_forbidden`(报告中出现了通过 Codex `/responses` 发送的 `messages[].assistant.reasoning_content`)。此前仅在 OpenAI 目标路径上剥离了 `reasoning_content`;同级回显字段 `reasoning`、`refusal`、`annotations` 和 `cache_control` 泄露了出去并触发了 422。现在在非推理 OpenAI 目标路径上全部丢弃。`audio` 被特意保留(OpenAI 音频模型在多轮中通过 id 引用先前的助手音频响应;Mistral 从不发出音频,因此不会有任何损失)。([#4350](https://github.com/diegosouzapw/OmniRoute/pull/4350) — 感谢 @xxy9468615) +- **fix(translator): 接受 AI SDK 风格的 `{ type: "image", image: "data:…" }` 内容部分** — 多个 OpenAI 输入翻译器仅识别形状为 `image_url.url`(或带有 `.source`/`.url` 的对象)的图像,因此 AI SDK 风格的部分(其中 `image` 是裸 data-URL 字符串)在到达视觉服务商之前被静默丢弃(OpenCode 是受影响客户端之一;此问题具有普遍性)。OpenAI→Claude、OpenAI→Kiro 和 OpenAI→Gemini/Antigravity 翻译器现在将字符串 `image` data URL 解析为各服务商的原生图像格式(Claude `{source:{type:"base64"}}`、Kiro `images[].source.bytes`、Gemini `inlineData`)。([#4345](https://github.com/diegosouzapw/OmniRoute/pull/4345) — 感谢 @mugnimaestra) +- **fix(translator): Gemini 接受 HTTP/HTTPS 图像 URL 而非静默丢弃** — OpenAI→Gemini 请求辅助函数(`convertOpenAIContentToParts`)之前会丢弃远程 `image_url` 部分(仅发出 `console.warn`),因为 Gemini 的 `inlineData` 需要 base64,而同步辅助函数无法获取并编码上游资源。现在对 HTTP/HTTPS URL 使用 Gemini 原生的 `fileData: { fileUri }` 部分(模型自行获取资源),因此携带 URL(而非 `data:` URI)的视觉请求能完整到达 Gemini。([#4373](https://github.com/diegosouzapw/OmniRoute/pull/4373) — 从 9router#344 移植,感谢 @diegosouzapw) +- **fix(executors): 为 qwen 非流式/thinking Claude-Code 请求剥离 `stream_options`** — Claude-Code 兼容服务商在 executor 级别强制开启 `stream` 标志,而出站请求体保留了调用方原始的 `stream: false`,因此 `DefaultExecutor.transformRequest` 向仍声明 `stream: false` 的请求体注入了 `stream_options: { include_usage: true }`,qwen 拒绝并返回 `400 "'stream_options' only set this when you set stream: true"`。executor 现在在请求体实际 `stream` 为 false 时剥离 `stream_options`。([#4374](https://github.com/diegosouzapw/OmniRoute/pull/4374) — 从 9router#663 移植,感谢 @anuragg-saxenaa / @diegosouzapw) +- **fix(executors): 当 `tool_choice` 强制使用工具时不再注入 `thinking`(原生 Claude)** — Claude-Code 网络镜像模拟会为非 Haiku 的 Claude 模型注入 `thinking: { type: "adaptive" }`,但 Anthropic 在 `tool_choice` 强制使用特定工具(`{type:"any"|"tool"}`)时会拒绝 `thinking`,返回 `400 "Thinking may not be enabled when tool_choice forces tool use."`。任何固定工具的 Opus/Sonnet 调用(例如 Claude Code 的 `message_user`,或强制使用工具的代理工具)都会遇到硬性 400 错误;当 `tool_choice` 强制使用工具时,现在会抑制该注入。([#4389](https://github.com/diegosouzapw/OmniRoute/pull/4389) — 感谢 @NomenAK) +- **fix(codex): 在 Codex Responses 请求中请求推理摘要** — Codex/OpenAI Responses 会返回推理 token 统计信息和空推理项,除非请求了可见的推理摘要,因此 Codex CLI / pi.dev 路径会丢失可见的思考文本。OmniRoute 现在在启用推理时请求 `reasoning.summary: "auto"`(并包含 `reasoning.encrypted_content`)— 保留客户端显式设置的 `reasoning.summary` 和现有的 `include` 条目,并在 `reasoning.effort: "none"` 时跳过。([#4359](https://github.com/diegosouzapw/OmniRoute/pull/4359) — 感谢 @xz-dev) +- **fix(sse): 将组合的每目标超时默认设为 120s 以实现快速故障转移** — 当组合未设置 `targetTimeoutMs` 时,组合的每目标超时继承了完整的 `FETCH_TIMEOUT_MS`(默认 600s),因此单个挂起/缓慢的目标(例如返回 524/504 的 openai-compatible 上游)可能使**整个**组合停滞长达 10 分钟才能进行故障转移。现在使用新的 `DEFAULT_COMBO_TARGET_TIMEOUT_MS = 120_000` 作为 `resolveComboTargetTimeoutMs` 中未设置时的默认值(向后兼容的第三个参数,在 `phaseComboSetup` 中接入);显式上限/退出选项得以保留。([#4365](https://github.com/diegosouzapw/OmniRoute/pull/4365) — 感谢 @diegosouzapw) +- **fix(cli): Tailscale 登录现在遵循 `TAILSCALE_AUTHKEY` 实现非交互式登录** — `startTailscaleLogin` 构建 `tailscale up` 时从未读取 `process.env.TAILSCALE_AUTHKEY`,因此在预先认证/无头守护进程上,登录会等待交互式认证 URL 并超时(约 15s)。当设置了 `TAILSCALE_AUTHKEY` 时,现在通过 `--auth-key=` 传递(作为 spawn argv 元素 — 不进行 shell 插值),守护进程可以非交互式认证;未设置时行为不变。([#4343](https://github.com/diegosouzapw/OmniRoute/pull/4343) — 感谢 @ipeterpetrus) +- **fix(dashboard): OAuth 模态框在非 JSON 服务器响应时显示真实错误** — OAuth 连接/重认证模态框无条件调用 `await res.json()`,因此当构建/OAuth 端点返回纯文本错误时(如 `500 Internal Server Error` 页面),模态框抛出 `Unexpected token 'I'…` 并隐藏了真实故障。两个共享辅助函数(`src/shared/utils/api.ts` 中的 `parseResponseBody` / `getErrorMessage`)现在安全地读取响应体(JSON 时按 JSON 解析,否则按原始文本处理),并在任何情况下都呈现清晰的消息;所有模态框的 fetch 调用点都使用了这些辅助函数。([#4351](https://github.com/diegosouzapw/OmniRoute/pull/4351) — 感谢 @DNNYF) +- **fix(dashboard): 已禁用连接的最近错误现在可见** — 服务商 卡片's 错误 徽章 counts a disabled 连接 (`isActive === false`) that has an 错误 (its effective status is still 错误/expired/unavailable), but the 连接 row hid the `lastError` text for disabled rows — so the operator saw the 错误 count without being able to see what failed. The row now shows the 错误 text whenever there is one, regardless of the active 开关. ([#4352](https://github.com/diegosouzapw/OmniRoute/pull/4352) — 感谢 @ntdung6868) +- **fix(providers): "逐一测试连接" OAuth 探测不再永久卡住队列** — OAuth 连接测试路径调用了裸 `fetch(url, { method, headers })`,没有 `AbortController`/signal/timeout,因此当服务商的探测端点接受 socket 但永不响应时,await 的 fetch 永远不会完成,逐一测试队列会无限期停滞(API-key 路径已通过 `validateProviderApiKey` 的 `timeoutMs` 设置了边界)。现在初始探测和刷新后的重试都设置了 `AbortSignal.timeout(30s)` 边界 — 与 API-key 路径的 30s 预算一致 — 超时探测会以失败形式返回,并附上清晰的 `Test timed out after 30s` 消息,格式与其他测试错误相同。([#4347](https://github.com/diegosouzapw/OmniRoute/pull/4347) — 感谢 @ntdung6868) +- **fix(providers): 已停用账户与已吊销 token 被区分标识** — 一个 OAuth 刷新完全正常但 ChatGPT 账户已被服务商停用的 Codex 连接会从上游 API 收到 `401`。连接测试将其与错误凭证等同标记(`Token invalid or revoked` → `upstream_auth_error`),因此操作者无法区分已停用账户和已吊销 token。现在测试会读取 `401`/`403` 的响应体,当表明账户停用时,将其归类为 `account_deactivated` — 控制台已将其呈现为"账户已停用"。普通认证 `401` 保持不变。([#4353](https://github.com/diegosouzapw/OmniRoute/pull/4353) — 感谢 @ntdung6868) +- **fix(db): 删除服务商时级联清理孤立的模型别名** — 删除自定义服务商时会移除其连接和节点,但会留下已导入的模型别名行(存储为 `key=`、`value="/<模型>"`)。这些过时的别名随后会阻止重新导入同一服务商 — 导入去重将它们视为"已存在",因此不会出现新的模型。新的 `deleteModelAliasesForProvider(providerId)` DB 辅助函数会删除所有存储值以 `/` 开头的别名(不影响其他服务商和用户定义的 settings 别名),服务商节点 DELETE 处理器现在在移除连接和节点后调用该函数,确保重新导入不受阻碍。([#4348](https://github.com/diegosouzapw/OmniRoute/pull/4348) — 感谢 @nguyenvanhuy0612) +- **fix(api): 添加自定义模型时持久化 `max_input_tokens` / `max_output_tokens`** — `POST /api/provider-models` 静默丢弃了"添加自定义模型"表单中设置的每模型 token 限制:处理器解构了请求体的其余部分,但从未读取 `max_input_tokens` / `max_output_tokens`,且 `addCustomModel()` 没有对应参数,因此这些值在写入时被丢弃。DB 层(`inputTokenLimit` / `outputTokenLimit`)和 `/v1/models` 目录已经能对这些字段进行往返 — 只有写入路径缺失。验证 schema 现在接受这两个可选限制,处理器转发它们,`addCustomModel()` 持久化它们,使自定义模型的上下文/输出窗口能保留在目录中。([#4349](https://github.com/diegosouzapw/OmniRoute/pull/4349) — 感谢 @codename-zen) +- **fix(plugin): OpenCode 静态目录插件为组合/原始模型键添加服务商 ID 前缀** — OpenCode 的静态目录读取器错误检测了 `omniroute` 服务商:以 `combo/MASTER` 形式发出的组合键被解析为服务商 `combo`("No credentials for provider: omniroute"),而裸 `MASTER` 形式被误读为无法解析服务商的模型,混合的 `omniroute/MASTER` + 裸原始键被 OpenCode 的 schema 拒绝。插件现在为每个组合和原始模型键添加 `omniroute` 服务商 ID 前缀,显式发出服务商 ID,并丢弃旧的 `combo/` 前缀 — 因此静态目录读取器能检测到服务商,认证加载器返回正确的凭证(目录获取超时也已提高,使冷启动服务器不会发布空存根)。([#4384](https://github.com/diegosouzapw/OmniRoute/pull/4384) — 感谢 @herjarsa) -### 🔒 Security +### 🔒 安全 -- **fix(security): scope the OAuth callback `postMessage` to a trusted-origin allowlist** — the OAuth callback at `/callback` previously posted `{ code, state, … }` to `window.opener.postMessage(…, "*")` whenever the opener was cross-origin, so a hostile page that opened the well-known redirect URI in a popup could receive the OAuth code/state and complete the flow as the user. The wildcard fallback is replaced with iteration over a fixed allowlist (same-origin + Codex's `localhost:1455` / `127.0.0.1:1455` loopback helper); the browser silently drops `postMessage` to any opener whose origin isn't listed. ([#4372](https://github.com/diegosouzapw/OmniRoute/pull/4372) — ported from 9router#998, thanks @aeonframework / @diegosouzapw) -- **fix(mitm): exact host membership in the MITM hosts test (CodeQL false positive)** — `tests/unit/mitm-tool-hosts.test.ts` checked host membership with `Array.includes(host)`, which CodeQL's `js/incomplete-url-substring-sanitization` heuristic misreads as a `String.includes()` URL-substring sanitization test (HIGH false positive). Switched to `.some((h) => h === host)` — identical semantics, no flagged pattern. ([#4386](https://github.com/diegosouzapw/OmniRoute/pull/4386)) +- **fix(security): 将 OAuth 回调的 `postMessage` 限定为可信来源白名单** — `/callback` 处的 OAuth 回调此前在 opener 跨域时向 `window.opener.postMessage(…, "*")` 发送 `{ code, state, … }`,因此恶意页面如果以弹窗形式打开众所周知的回调 URI,就能接收 OAuth code/state 并以用户身份完成流程。通配符回退已被替换为遍历固定白名单(同源 + Codex 的 `localhost:1455` / `127.0.0.1:1455` loopback helper);浏览器会静默丢弃向白名单外的任何 opener origin 发送的 `postMessage`。([#4372](https://github.com/diegosouzapw/OmniRoute/pull/4372) — 从 9router#998 移植,感谢 @aeonframework / @diegosouzapw) +- **fix(mitm): 在 MITM 主机测试中使用精确主机匹配(CodeQL 误报)** — `tests/unit/mitm-tool-hosts.test.ts` 使用 `Array.includes(host)` 检查主机成员,CodeQL 的 `js/incomplete-url-substring-sanitization` 启发式规则将其误读为 `String.includes()` URL 子串清理测试(严重误报)。切换为 `.some((h) => h === host)` — 语义相同,且不会触发标志模式。([#4386](https://github.com/diegosouzapw/OmniRoute/pull/4386)) -### 📝 Maintenance +### 📝 维护 -- **docs: one-time feature-documentation catch-up (v3.8.20 → v3.8.30)** — reconciled the docs with every user-facing feature shipped since v3.8.20: a new README **✨ What's New** section; new guides for [CLI integrations](docs/guides/CLI-INTEGRATIONS.md), [MITM TPROXY transparent decrypt](docs/security/MITM-TPROXY-DECRYPT.md) and [delegated Anthropic Context Editing](docs/compression/CONTEXT_EDITING.md); refreshed AUTO-COMBO (`auto/:` + Arena-ELO), API_REFERENCE (`x-omniroute-no-memory`), MEMORY (int8 quantization, off-by-default), RESILIENCE (model-lockout success-decay), RTK, AGENTBRIDGE, TRAFFIC_INSPECTOR, GUARDRAILS, CLOUD_AGENT, ENVIRONMENT; regenerated PROVIDER_REFERENCE (231 providers) and synced the provider count in README/CLAUDE/AGENTS. Going forward this runs every release (generate-release step 6b). ([#4391](https://github.com/diegosouzapw/OmniRoute/pull/4391)) -- **refactor(chatCore): extract the `checkHeapPressureGuard` leaf (god-file decomposition start)** — first increment of decomposing `chatCore.ts` (~5127 LOC, the hottest path — every chat request flows through `handleChatCore`). The V8 heap-pressure guard at the top of `handleChatCore` (rejects with 503 when `heapUsed` exceeds the shed threshold) is moved to a self-contained, co-located `utils/heapPressure.ts::checkHeapPressureGuard(...)` with no behavior change. ([#4371](https://github.com/diegosouzapw/OmniRoute/pull/4371) — thanks @diegosouzapw) -- **refactor(combo): de-dup the exhausted-target skip predicate across both dispatchers** — the byte-identical `#1731`/`#1731v2` pre-check (skip a target already exhausted on the provider/connection within a request) lived in both combo dispatchers; extracted to a shared `combo/comboPredicates.ts` helper. ([#4362](https://github.com/diegosouzapw/OmniRoute/pull/4362) — thanks @diegosouzapw) -- **refactor(combo): de-dup the upstream-error exhaustion classification across both dispatchers** — both dispatchers ran a near-identical post-error block classifying the upstream error and updating the exhaustion Sets (`#1731` provider exhausted / `#1731v2` connection error / transient rate-limited); extracted to a shared `combo/targetExhaustion.ts::applyComboTargetExhaustion(...)`. ([#4366](https://github.com/diegosouzapw/OmniRoute/pull/4366) — thanks @diegosouzapw) -- **chore(cli): localize CLI / scraping copy and stabilize fetch, memory & coverage handling** — localizes CLI and scraping UX copy plus the Adapta onboarding tutorial (and corrects the CLI Code page title), makes fetch retries honor the start timeout, tightens SSE/response typing, respects configured memory token limits during search, and reduces CI coverage-merge memory by merging V8 data incrementally. ([#4383](https://github.com/diegosouzapw/OmniRoute/pull/4383) — thanks @JxnLexn) -- **test(combo): reset circuit breakers between stream-readiness cases (restore green)** — a stream-readiness fallback case failed on the release branch since the cycle-open tip due to test isolation: earlier combo-dispatch cases in the same file deliberately fail `glm` (tripping the module-level provider circuit breaker), and that OPEN state leaked into the next test so `combo.ts` skipped the model. The test now resets the circuit breakers between cases. ([#4396](https://github.com/diegosouzapw/OmniRoute/pull/4396) — thanks @diegosouzapw) -- **chore(quality): reconcile the complexity ratchet baseline (1896 → 1900)** — absorbs the small complexity-metric increase from the v3.8.31 `/review-prs` merge batch into `quality-baseline.json` so the ratchet reflects the shipped code (no production change). ([#4410](https://github.com/diegosouzapw/OmniRoute/pull/4410) — thanks @diegosouzapw) -- **test/gate: reconcile release-time drift surfaced by the full CI gate** — three already-merged changes left the release branch's full-CI gate red (the per-PR fast gates don't run it): the Gemini `convertOpenAIContentToParts` tests were realigned to the [#4373](https://github.com/diegosouzapw/OmniRoute/pull/4373) HTTP/HTTPS-URL `fileData` pass-through (they still asserted the old warn-and-drop behavior), the `t11` any-budget for `open-sse/executors/base.ts` was raised to 2 with a justification ([#4389](https://github.com/diegosouzapw/OmniRoute/pull/4389) compares `tool_choice` against the string literal `"any"`, not a TS `any` type), and the [#4384](https://github.com/diegosouzapw/OmniRoute/pull/4384) opencode-plugin combos test's net-assert reduction (dropping the obsolete `combo/` namespace) was allowlisted. No production behavior change. (thanks @diegosouzapw) +- **docs: 一次性功能文档补全(v3.8.20 → v3.8.30)** — 将所有自 v3.8.20 以来发布的面向用户功能与文档对齐:新增 README **✨ What's New** 章节;新增 [CLI 集成](docs/guides/CLI-INTEGRATIONS.md)、[MITM TPROXY 透明解密](docs/security/MITM-TPROXY-DECRYPT.md)和[代理 Anthropic 上下文编辑](docs/压缩/CONTEXT_EDITING.md)指南;刷新 AUTO-COMBO(`auto/:` + Arena-ELO)、API_REFERENCE(`x-omniroute-no-memory`)、MEMORY(int8 量化,默认关闭)、RESILIENCE(模型锁定成功衰减)、RTK、AGENTBRIDGE、TRAFFIC_INSPECTOR、GUARDRAILS、CLOUD_AGENT、ENVIRONMENT;重新生成 PROVIDER_REFERENCE(231 服务商)并同步 README/CLAUDE/AGENTS 中的服务商数量。今后每次发版都会运行此项(generate-release 步骤 6b)。([#4391](https://github.com/diegosouzapw/OmniRoute/pull/4391)) +- **refactor(chatCore): 提取 `checkHeapPressureGuard` 叶子函数(巨型文件分解的开始)** — 分解 `chatCore.ts`(约 5127 行,最热路径 — 每个聊天请求都经过 `handleChatCore`)的第一步。`handleChatCore` 顶部的 V8 堆内存压力守卫(当 `heapUsed` 超过丢弃阈值时拒绝并返回 503)被移至一个独立、同目录的 `utils/heapPressure.ts::checkHeapPressureGuard(...)`,行为无变化。([#4371](https://github.com/diegosouzapw/OmniRoute/pull/4371) — 感谢 @diegosouzapw) +- **refactor(combo): 在两个调度器中消除已耗尽目标跳过谓词的重复** — 字节完全相同的 `#1731`/`#1731v2` 预检查(跳过请求中已在服务商/连接上耗尽的目标)存在于两个组合调度器中;已提取为共享的 `Combo/comboPredicates.ts` 辅助函数。([#4362](https://github.com/diegosouzapw/OmniRoute/pull/4362) — 感谢 @diegosouzapw) +- **refactor(combo): 在两个调度器中消除上游错误耗尽分类的重复** — 两个调度器都运行了几乎相同的错误后处理代码块,对上游错误进行分类并更新耗尽集合(`#1731` 服务商耗尽 / `#1731v2` 连接错误 / 瞬时速率限制);已提取为共享的 `Combo/targetExhaustion.ts::applyComboTargetExhaustion(...)`。([#4366](https://github.com/diegosouzapw/OmniRoute/pull/4366) — 感谢 @diegosouzapw) +- **chore(cli): 本地化 CLI / 抓取文案并稳定 fetch、内存和覆盖率处理** — 本地化 CLI 和抓取 UX 文案以及 Adapta 入门教程(并修正 CLI Code 页面标题),使 fetch 重试遵循启动超时,收紧 SSE/响应类型,在搜索期间遵循配置的内存 token 限制,并通过增量合并 V8 数据减少 CI 覆盖率合并内存开销。([#4383](https://github.com/diegosouzapw/OmniRoute/pull/4383) — 感谢 @JxnLexn) +- **test(combo): 在流就绪测试用例之间重置熔断器(恢复绿色状态)** — 一个流就绪回退用例在发版分支上失败,原因是测试隔离问题:同一文件中较早的组合调度用例故意使 `glm` 失败(触发模块级服务商熔断器),该 OPEN 状态泄露到了下一个测试,因此 `Combo.ts` 跳过了模型。测试现在在用例之间重置熔断器。([#4396](https://github.com/diegosouzapw/OmniRoute/pull/4396) — 感谢 @diegosouzapw) +- **chore(quality): 对齐复杂度基准线(1896 → 1900)** — 将 v3.8.31 `/review-prs` 合并批次带来的小幅复杂度指标增长吸收到 `quality-baseline.json` 中,使基准线反映已发布的代码(无生产变更)。([#4410](https://github.com/diegosouzapw/OmniRoute/pull/4410) — 感谢 @diegosouzapw) +- **test/gate: 对齐完整 CI 门禁发现的发版时漂移** — 三个已合并的变更导致发版分支的完整 CI 门禁变红(各 PR 的快速门禁不会运行):Gemini `convertOpenAIContentToParts` 测试已与 [#4373](https://github.com/diegosouzapw/OmniRoute/pull/4373) 的 HTTP/HTTPS-URL `fileData` 透传对齐(它们之前仍断言旧的 warn-and-drop 行为),`open-sse/executors/base.ts` 的 `t11` any 预算提升至 2 并附有理由([#4389](https://github.com/diegosouzapw/OmniRoute/pull/4389) 将 `tool_choice` 与字符串字面量 `"any"` 比较,而非 TS `any` 类型),[#4384](https://github.com/diegosouzapw/OmniRoute/pull/4384) 的 opencode 插件组合测试的网络断言减少(删除已废弃的 `Combo/` 命名空间)已加入白名单。无生产行为变更。(感谢 @diegosouzapw) --- ## [3.8.30] — 2026-06-20 -### ✨ New Features +### ✨ 新功能 -- **feat(dashboard): category (media serviceKind) filter on the providers page** — `/dashboard/providers` gains a media-category filter row (Image / Video / Music / Text→Speech / Speech→Text / Embedding) that composes with the existing search, free-only and "show configured only" filters. Membership is derived from the backend media registries (a provider that serves a kind is surfaced even if it never declared `serviceKinds`), keeping the UI in lockstep with the backend. ([#4240](https://github.com/diegosouzapw/OmniRoute/issues/4240)) -- **feat(combo): per-step account allowlist — scope a round-robin/weighted step to a subset of a provider's connections** — a combo model step can now carry a first-class account allowlist so a round-robin (or weighted) strategy is scoped to a chosen subset of a provider's connections (e.g. only `foo1`+`foo2` out of `foo1..foo4`) without hand-pinning one step per account. Empty = the whole active pool (unchanged). When a step both has an allowlist and is tag-routed, the two intersect (most-restrictive wins); a single pinned account still takes precedence. The combo builder's Precision step editor gains an optional "Restrict to accounts" picker. ([#3266](https://github.com/diegosouzapw/OmniRoute/issues/3266)) -- **feat(providers): add OpenAdapter, dit.ai and TokenRouter as OpenAI-compatible providers** — three community-requested OpenAI-compatible aggregators now register as standard named OpenAI-style providers with live `/v1/models` discovery (the zenmux pattern), falling back to a seeded catalog when the upstream list is unavailable: **OpenAdapter** (`https://api.openadapter.in/v1`, free tier, 70+ open-source models — [#4239](https://github.com/diegosouzapw/OmniRoute/issues/4239)), **dit.ai** (`https://api.dit.ai/v1`, dynamic-pricing router/gateway — [#4155](https://github.com/diegosouzapw/OmniRoute/issues/4155)), and **TokenRouter** (`https://api.tokenrouter.com/v1`, free MiniMax model — [#3841](https://github.com/diegosouzapw/OmniRoute/issues/3841), thanks @FerLuisxd). No custom executor/translator — default OpenAI passthrough. -- **feat(api): `x-omniroute-no-memory` request header — per-request opt-out of memory/skills injection** — clients that manage their own context (e.g. their own RAG/memory) can send `x-omniroute-no-memory: true` (mirrors the existing `x-omniroute-no-cache` convention) to skip the gateway injecting up to `memorySettings.maxTokens` (~2k) tokens of memory **and** skills context into that chat request — avoiding the token/cost inflation it otherwise adds on every call. Absent the header, behavior is unchanged. (PRD-2026-06-19-no-memory-header) -- **feat(dashboard): MITM tool card lists the exact hosts-file entries to add manually** — the CLI-tools MITM card's "How it works" section now lists the full set of `127.0.0.1 ` lines for the selected tool (sourced from the canonical MITM target registry) instead of a single example domain. Users on locked-down machines — where the automatic, sudo-gated hosts-file edit isn't available — can now copy every required entry by hand. (thanks @mrcyclo) -- **feat(cli): `omniroute launch-codex` + `setup-codex` — run/configure the Codex CLI against OmniRoute** — a launcher and setup command that point the Codex CLI at an OmniRoute endpoint (remote-mode aware). ([#4270](https://github.com/diegosouzapw/OmniRoute/pull/4270)) -- **feat(cli): Claude Code launcher + setup — remote mode + profiles** — `omniroute launch`/`setup` for Claude Code with remote-mode support and named connection profiles. ([#4274](https://github.com/diegosouzapw/OmniRoute/pull/4274)) -- **feat(cli): OpenCode setup — OpenAI-compatible provider + remote-aware plugin** — `setup-opencode` registers OmniRoute as an OpenAI-compatible provider for OpenCode and installs a remote-aware plugin. ([#4277](https://github.com/diegosouzapw/OmniRoute/pull/4277)) -- **feat(cli): one-command setup for popular AI coding tools** — new `setup-*` commands that configure each tool to talk to OmniRoute: **Cline** ([#4280](https://github.com/diegosouzapw/OmniRoute/pull/4280)), **Kilo Code** ([#4284](https://github.com/diegosouzapw/OmniRoute/pull/4284)), **Continue** ([#4289](https://github.com/diegosouzapw/OmniRoute/pull/4289)), **Cursor** ([#4291](https://github.com/diegosouzapw/OmniRoute/pull/4291)), **Roo Code** ([#4292](https://github.com/diegosouzapw/OmniRoute/pull/4292)), **Crush** ([#4298](https://github.com/diegosouzapw/OmniRoute/pull/4298)), **Goose** ([#4300](https://github.com/diegosouzapw/OmniRoute/pull/4300)), **Qwen Code** ([#4301](https://github.com/diegosouzapw/OmniRoute/pull/4301)), **Aider** ([#4302](https://github.com/diegosouzapw/OmniRoute/pull/4302)) and the **Gemini CLI** (native `/v1beta`) ([#4303](https://github.com/diegosouzapw/OmniRoute/pull/4303)). -- **feat(providers): provider model sweep — live discovery, refreshed catalogs, dead-provider cleanup** — a broad sweep that enables live `/v1/models` discovery for more OpenAI-style providers (the zenmux pattern), refreshes the seeded catalogs with current models, and marks dead providers `deprecated`. ([#4324](https://github.com/diegosouzapw/OmniRoute/pull/4324)) -- **feat(mitm): translate Antigravity cloudcode end-to-end (Gap B)** — the MITM decrypt path now translates Antigravity `cloudcode` traffic end-to-end. ([#4299](https://github.com/diegosouzapw/OmniRoute/pull/4299)) -- **feat(keys): per-key USD usage quota controls** — an API key can now carry a USD spend quota that caps its usage once the threshold is reached. ([#4327](https://github.com/diegosouzapw/OmniRoute/pull/4327) — thanks @Witroch4) +- **feat(dashboard): 服务商页面的媒体类别 (media serviceKind) 筛选器** — `/dashboard/providers` 新增媒体类别筛选行(Image / Video / Music / Text→Speech / Speech→Text / Embedding),与现有的搜索、仅显示免费和"仅显示已配置"筛选器组合使用。类别归属从后端媒体注册表推导(即使服务商未声明 `serviceKinds`,只要它实际提供某种媒体服务就会被列出),确保 UI 与后端保持同步。([#4240](https://github.com/diegosouzapw/OmniRoute/issues/4240)) +- **feat(combo): 按步骤的账户允许列表 — 将轮询/加权步骤范围限定到服务商连接的子集** — Combo 模型步骤现在可以携带一等公民的账户允许列表,这样轮询(或加权)策略可以限定到服务商连接的一个选定子集(例如只选择 `foo1`+`foo2` 而不是 `foo1..foo4`),而无需为每个账户手动固定一个步骤。留空 = 使用全部活跃池(行为不变)。当步骤同时具有允许列表和标签路由时,两者取交集(最严格者胜);单个固定账户仍然优先。Combo 构建器的 Precision 步骤编辑器新增可选的"限制到特定账户"选择器。([#3266](https://github.com/diegosouzapw/OmniRoute/issues/3266)) +- **feat(providers): 新增 OpenAdapter、dit.ai 和 TokenRouter 作为 OpenAI 兼容服务商** — 三个社区请求的 OpenAI 兼容聚合器现在注册为标准命名的 OpenAI 风格服务商,支持实时 `/v1/模型` 发现(zenmux 模式),在上游列表不可用时回退到种子目录:**OpenAdapter**(`https://api.openadapter.in/v1`,免费层级,70+ 开源模型 — [#4239](https://github.com/diegosouzapw/OmniRoute/issues/4239))、**dit.ai**(`https://api.dit.ai/v1`,动态定价路由器/网关 — [#4155](https://github.com/diegosouzapw/OmniRoute/issues/4155))、**TokenRouter**(`https://api.tokenrouter.com/v1`,免费 MiniMax 模型 — [#3841](https://github.com/diegosouzapw/OmniRoute/issues/3841),感谢 @FerLuisxd)。无需自定义执行器/翻译器 — 默认 OpenAI 直通。 +- **feat(api): `x-omniroute-no-memory` 请求头 — 按请求选择退出记忆/技能注入** — 自行管理上下文的客户端(例如自有的 RAG/记忆系统)可以发送 `x-omniroute-no-memory: true`(沿用现有的 `x-omniroute-no-缓存` 约定),跳过网关将最多 `memorySettings.maxTokens`(约 2k)tokens 的记忆**和**技能上下文注入到该聊天请求中——避免每次调用带来的 token/成本膨胀。不发送此头时行为不变。(PRD-2026-06-19-no-memory-头) +- **feat(dashboard): MITM 工具卡片列出需要手动添加的完整 hosts 文件条目** — CLI 工具的 MITM 卡片中"工作原理"部分现在列出所选工具的完整 `127.0.0.1 ` 行集合(源自规范的 MITM 目标注册表),而非仅显示单个示例域名。在锁定机器上 — 即自动的、需要 sudo 权限的 hosts 文件编辑不可用时 — 用户现在可以手动复制每个必需条目。(感谢 @mrcyclo) +- **feat(cli): `omniroute launch-codex` + `setup-codex` — 运行/配置 Codex CLI 对接 OmniRoute** — 启动器和设置命令,将 Codex CLI 指向 OmniRoute 端点(支持远程模式)。([#4270](https://github.com/diegosouzapw/OmniRoute/pull/4270)) +- **feat(cli): Claude Code 启动器 + 设置 — 远程模式 + 配置文件** — `omniroute launch`/`setup` 用于 Claude Code,支持远程模式和命名连接配置文件。([#4274](https://github.com/diegosouzapw/OmniRoute/pull/4274)) +- **feat(cli): OpenCode 设置 — OpenAI 兼容服务商 + 远程感知插件** — `setup-opencode` 将 OmniRoute 注册为 OpenCode 的 OpenAI 兼容服务商,并安装远程感知插件。([#4277](https://github.com/diegosouzapw/OmniRoute/pull/4277)) +- **feat(cli): 流行 AI 编码工具的一键设置命令** — 新增 `setup-*` 命令,配置各工具对接 OmniRoute:**Cline** ([#4280](https://github.com/diegosouzapw/OmniRoute/pull/4280))、**Kilo Code** ([#4284](https://github.com/diegosouzapw/OmniRoute/pull/4284))、**Continue** ([#4289](https://github.com/diegosouzapw/OmniRoute/pull/4289))、**Cursor** ([#4291](https://github.com/diegosouzapw/OmniRoute/pull/4291))、**Roo Code** ([#4292](https://github.com/diegosouzapw/OmniRoute/pull/4292))、**Crush** ([#4298](https://github.com/diegosouzapw/OmniRoute/pull/4298))、**Goose** ([#4300](https://github.com/diegosouzapw/OmniRoute/pull/4300))、**Qwen Code** ([#4301](https://github.com/diegosouzapw/OmniRoute/pull/4301))、**Aider** ([#4302](https://github.com/diegosouzapw/OmniRoute/pull/4302)) 以及 **Gemini CLI**(原生 `/v1beta`)([#4303](https://github.com/diegosouzapw/OmniRoute/pull/4303))。 +- **feat(providers): 服务商模型全面扫描 — 实时发现、刷新目录、清理失效服务商** — 一次大规模扫描,为更多 OpenAI 风格服务商启用实时 `/v1/模型` 发现(zenmux 模式),用最新模型刷新种子目录,并将失效服务商标记为 `deprecated`。([#4324](https://github.com/diegosouzapw/OmniRoute/pull/4324)) +- **feat(mitm): 端到端翻译 Antigravity cloudcode 流量(Gap B)** — MITM 解密路径现在端到端翻译 Antigravity `cloudcode` 流量。([#4299](https://github.com/diegosouzapw/OmniRoute/pull/4299)) +- **feat(keys): 按 API 密钥的美元使用配额控制** — API 密钥现在可以携带美元消费配额,在达到阈值后限制其使用。([#4327](https://github.com/diegosouzapw/OmniRoute/pull/4327) — 感谢 @Witroch4) -### 🔧 Changed +### 🔧 变更 -- **change(memory): memory is now OFF by default** — `DEFAULT_MEMORY_SETTINGS.enabled` now defaults to `false`. Enabling memory injects up to ~2,000 tokens of retrieved context into **every** chat request (and that context is billed), which was a surprising default for new installs and for clients with their own context. Memory is now an explicit opt-in: installs that already enabled it keep it on; installs that never configured it default to off. The Settings → Memory panel now shows a token-cost warning when memory is enabled. (PRD-2026-06-19-no-memory-header) +- **change(memory): 记忆功能现在默认关闭** — `DEFAULT_MEMORY_SETTINGS.enabled` 现在默认为 `false`。启用记忆功能会向**每个**聊天请求注入最多约 2,000 个 token 的检索上下文(该上下文会被计费),这对于新安装和拥有自己上下文的客户端来说是一个意外的默认行为。记忆功能现在需要显式选择启用:已启用它的安装保持不变;从未配置过的安装默认为关闭。设置 → 记忆面板现在在启用记忆功能时显示 token 成本警告。(PRD-2026-06-19-no-memory-header) -### 🐛 Fixed +### 🐛 问题修复 -- **fix(translator): Gemini accepts HTTP/HTTPS image URLs (no longer silently dropped)** — OpenAI-style `image_url` parts whose URL was `http://…` or `https://…` reached `convertOpenAIContentToParts` (the OpenAI→Gemini request helper) and were dropped with only a `console.warn`, because Gemini's `inlineData` requires base64 and the helper is synchronous (it cannot fetch + encode). Gemini's `Part` schema, however, natively accepts `fileData: { fileUri }` for remote URIs — the model fetches the asset itself. The helper now emits a `fileData` part (`mimeType: "image/*"`, inferred upstream on fetch) instead of dropping, so vision requests that pass a URL — not a data: URI — now reach Gemini intact. `data:` URIs still go through `inlineData` unchanged; unsupported schemes (e.g. `ftp:`) are still skipped. (thanks @East-rayyy) -- **fix(security): OAuth callback page no longer relays `code`/`state` to a wildcard `postMessage` target** — the OAuth callback at `/callback` posted `{ code, state, ... }` to `window.opener.postMessage(..., "*")` whenever the opener was cross-origin (a fallback for the legitimate remote-dashboard + local-loopback callback scenario). A hostile page that opened the callback URL in a popup against the well-known redirect URI would therefore receive the OAuth code+state and could complete the OAuth flow as the user. The wildcard fallback is replaced with an iteration over a fixed allowlist of trusted target origins (same-origin + Codex's loopback helper at `localhost:1455` / `127.0.0.1:1455`); the browser silently drops the message for any opener whose origin is not in the list. Methods 2 (`BroadcastChannel`) and 3 (`localStorage`) — already in the page — still cover same-origin parents when the opener was severed by COOP. (thanks @aeonframework) -- **fix(compliance): startup cleanup honors the dashboard data-retention setting instead of always trimming to 7 days** — on every restart, `cleanupExpiredLogs()` (run at startup) read retention only from the `CALL_LOG_RETENTION_DAYS` / `APP_LOG_RETENTION_DAYS` env vars, which default to **7 days** when unset, and trimmed `usage_history` (the Usage Analysis data) before the dashboard-based `runAutoCleanup()` — which respects the configured retention — ever ran. So a dashboard "Data Retention" of 90 days was silently overridden and the Usage Analysis page only ever showed the last 7 days after a restart. Retention now follows the precedence **explicit env var → dashboard DB setting → 7-day default**, per table (`usage_history`→`usageHistory`, `call_logs`/`proxy_logs`/`request_detail_logs`→`callLogs`, `mcp_tool_audit`→`mcpAudit`); an operator who sets the env var still wins, and non-DB deployments still fall back to it. ([#4354](https://github.com/diegosouzapw/OmniRoute/issues/4354) — thanks @akbardwi) -- **fix(providers): bailian-coding-plan static fallback catalog matches the registry (10 models)** — the provider-model sweep (#4324) added four current Model Studio coding-plan models (`qwen3.7-plus`, `qwen3-coder-plus`, `qwen3-coder-next`, `glm-4.7`) to the `bailian-coding-plan` registry entry but missed the static fallback mirror in `staticModels.ts`, which still listed only the older six. The static catalog (served when live discovery is unavailable) therefore diverged from the registry, and the existing static↔registry parity test went red on the release branch (only surfacing when test-impact analysis happened to select it). The static mirror now carries all ten models in registry order, restoring parity. ([#4324](https://github.com/diegosouzapw/OmniRoute/pull/4324)) -- **fix(executors): ArenaLLM accepts LMArena's split Supabase SSR auth cookie** — LMArena migrated to `@supabase/ssr` chunked auth cookies: the single `arena-auth-prod-v1` cookie is now empty and the real session is split across `arena-auth-prod-v1.0`, `arena-auth-prod-v1.1`, … (ascending). A user who pasted the (now-empty) single cookie therefore sent an empty session and upstream rejected it as "invalid cookie". The LMArena executor now reconstructs the single cookie from its chunks — reading `.0`, `.1`, … in ascending numeric order until one is missing and concatenating their raw values (`@supabase/ssr`'s `combineChunks` rule: plain `join("")`, no base64-decode, no JSON-parse, the `base64-` prefix kept verbatim) — while preserving the rest of the pasted jar. A non-empty single cookie is still forwarded unchanged (back-compat). The credential UX now instructs pasting the **full Cookie header** and tracks the `.0`/`.1` storage keys. ([#4271](https://github.com/diegosouzapw/OmniRoute/issues/4271) — thanks @caussao) -- **fix(compression): preserve the cacheable prefix for automatic-cache providers** — OpenAI / Codex (and Azure-OpenAI) use _automatic_ prefix caching: the upstream caches the longest matching prefix of a request (system prompt + earliest messages) **without** any explicit `cache_control` markers in the body. The cache-aware compression guard only protected that prefix when the request carried explicit `cache_control`, so for automatic-cache providers the guard was skipped — and with compression enabled and `preserveSystemPrompt: false` (or a prefix-compressing mode like `aggressive`/`ultra`) it rewrote the system prompt / earliest messages, guaranteeing a cache miss and **higher** token spend through OmniRoute than going direct. The guard now treats a caching provider as sufficient on its own (`isCachingProvider` alone, independent of `cache_control`) to skip the system prompt and downgrade prefix-compressing modes, and OpenAI/Codex/Azure are now recognized as caching providers. Compression is still off by default — this only affects operators who enabled it with prefix preservation turned off. ([#3955](https://github.com/diegosouzapw/OmniRoute/issues/3955)) -- **fix(executors): DuckDuckGo AI Chat uses duckduckgo.com (fixes 400)** — the DuckDuckGo AI Chat executor fetched status/chat and set `Origin`/`Referer` against `https://duck.ai` while still sending `Sec-Fetch-Site: same-origin`, so the request's same-origin triplet (host + Origin + Referer) was inconsistent and the backend rejected it with HTTP 400. All current DDG reverse-engineering references — and the provider registry's own `baseUrl` — use `https://duckduckgo.com`; the executor now uses it consistently for the status URL, chat URL, `Origin`, and `Referer` (the same-origin header is now coherent). The `x-fe-version` scrape regex also required a 40-hex tail but the real served token has a 20-hex tail (e.g. `serp_20250401_100419_ET-19d438eb199b2bf7c300`), so it silently fell back to a hardcoded default; the pattern is relaxed to a bounded `{20,40}` tail (still ReDoS-safe). This addresses the DuckDuckGo half of the report; the separate Chipotle/`chipotle` upstream breakage is tracked independently. ([#4037](https://github.com/diegosouzapw/OmniRoute/issues/4037) — thanks @daniij) -- **fix(security): bound the prompt-injection scan to the first 16 KB (hot-path perf)** — the prompt-injection guard joined every message/system string into one buffer and ran several regexes over the **whole** thing on every chat request, with no size cap — so a 300 KB body (pasted code, RAG context) meant O(body) CPU scanning on the hot path, a self-inflicted latency/GC source under concurrency. Both detection call sites (`detectInjection` in `inputSanitizer.ts` and the custom-pattern scan in `promptInjection.ts`) now slice the joined text to the first **16 KB** (`MAX_INJECTION_SCAN_BYTES`) before the regex loop. Injection directives sit near the top of a prompt, so the generous cap preserves real detection while scanning only a bounded prefix; the existing 10 MB body-size cap (which protects ingestion) is unchanged. ([#3932](https://github.com/diegosouzapw/OmniRoute/issues/3932) — thanks @KooshaPari) -- **fix(sse): retry direct-connection socket failures on a fresh socket (fewer `502` bursts)** — the default direct-connection undici dispatcher pools keep-alive sockets for up to 4 s, but some edges (e.g. `nvidia`, `opencode-zen`) silently close idle keep-alive sockets within that window, so the next request reusing a pooled socket fails with `UND_ERR_SOCKET` ("other side closed") — in bursts. `proxyFetch` already retried once on such transient errors, but the retry reused the **same** pooled dispatcher and could grab another stale socket, then fell through to native fetch (which also pools) → the job sat in the rate-limit queue until the 30 s timeout → `502` + circuit-breaker open. The retry now uses a dedicated **no-keep-alive / no-pipelining** dispatcher so it opens a brand-new socket that can't be a dead pooled one; the first attempt still uses the pooled dispatcher (healthy keep-alive reuse is preserved). Complements the v3.8.29 diagnostics (`describeFetchCause`, #4281). ([#4252](https://github.com/diegosouzapw/OmniRoute/issues/4252) — thanks @klimadev) -- **fix(sse): combo now stops at the first body-specific 400 instead of trying every target** — the `#2101` guard that detects a body-specific 400 (context overflow / malformed / model-access-denied, e.g. "model is not supported when using Codex with a ChatGPT account") logged "stopping combo" but executed a bare `break`, which only exited the inner retry loop; `executeTarget` then returned `null` and the outer target loop treated that as "this target produced nothing" and advanced to the next model. A combo of N targets that all reject the same request body therefore marched through all N (the report shows a 143-model Codex combo iterating every target), wasting upstream calls and per-attempt work. The guard now surfaces the 400 via the `{ ok, response }` contract (mirroring the 499 client-disconnect path) so the combo resolves and stops immediately. ([#4279](https://github.com/diegosouzapw/OmniRoute/issues/4279)) -- **fix(sse): non-streaming combo over a Responses-API target no longer returns empty content** — a Responses-API target (codex/`cx`) streams from upstream even on `stream:false`, and its terminal `response.completed` snapshot can carry a non-empty `output` that lacks the assistant message item (e.g. only a `reasoning` item) while the streamed `output_text` deltas had reconstructed the full message. The SSE→JSON aggregator preferred the terminal `output` wholesale, dropping the reconstructed text → HTTP 200 with empty content (hit notably via n8n, which defaults to `stream:false`). The aggregator now falls back to the reconstructed delta output when the terminal output has no message item but the reconstruction does; the terminal snapshot still wins whenever it already carries the message. ([#3948](https://github.com/diegosouzapw/OmniRoute/issues/3948)) -- **fix(executors): preserve tool-name casing on native Claude OAuth (`read` no longer leaks back as `Read`)** — native Claude OAuth traffic runs through an anti-fingerprint tool-name cloak that renames a tool literally named `read` to `Read` on the wire and records the reverse alias on a non-enumerable `_toolNameMap`, which the response side uses to restore the client's original casing. Since v3.8.27 the executor returned a JSON-round-tripped copy of the body as `transformedBody`, and that round-trip dropped the non-enumerable map — so the restore saw an empty map and the cloaked `Read` streamed verbatim to the client, corrupting the tool name. The executor now re-attaches the cloak map onto the serialized body (mirroring the Antigravity executor), so tool-name casing round-trips correctly. ([#4307](https://github.com/diegosouzapw/OmniRoute/issues/4307) — thanks @dev-cj) -- **fix(api): cache-HIT `X-OmniRoute-Response-Cost` now reports the incremental cost (≈0), not the original** — on a semantic-cache HIT the gateway serves the stored response **without** an upstream call, but `X-OmniRoute-Response-Cost` was reporting the original call's full cost (recomputed from the cached `usage`). A consumer summing `response-cost` for billing was therefore charging for responses that cost ≈$0 to serve (and stale entries could inflate it). Cache hits now bill `X-OmniRoute-Response-Cost: 0.0000000000` (the real incremental cost), and the avoided cost is surfaced in a new **`X-OmniRoute-Cost-Saved`** header for cache analytics — mirroring the existing `tokens_saved` concept. The MISS path is unchanged. (PRD-2026-06-19-cache-hit-cost-reporting) -- **fix(models): imported vision-capable models keep their vision capability** — after importing a provider key, vision-capable models (e.g. OpenRouter models whose `architecture` declares image input, and other synced providers) were listed as text-only in `/v1/models` and the dashboard — even though image requests actually worked. Synced model records never captured the vision flag, and the catalog's OpenRouter live-enrichment (which derives vision from `architecture.input_modalities`) is skipped once a provider has synced models. Discovery now captures `supportsVision` at sync time (from `architecture.input_modalities`, the string `architecture.modality`, or a top-level `input_modalities`), mirroring the existing `supportsThinking` capture, and the catalog surfaces `capabilities.vision` for synced models. ([#4264](https://github.com/diegosouzapw/OmniRoute/issues/4264) — thanks @FerLuisxd) -- **fix(providers): Cloudflare Workers AI model discovery shows model names, not UUIDs** — importing a Cloudflare Workers AI key listed models with internal UUID identifiers (e.g. `429b9e8b-d99e-…`) instead of their usable slugs (`@cf/meta/llama-3.1-8b-instruct`). Cloudflare's `/ai/models/search` returns `{ id: "", name: "@cf/…" }`, and discovery was passing the raw objects through — so the UUID `id` became the callable model id. The `cloudflare-ai` discovery now maps each result's `name` → id, surfacing the real `@cf/…` model ids. ([#4259](https://github.com/diegosouzapw/OmniRoute/issues/4259) — thanks @FerLuisxd) -- **fix(translator): clamp Responses API `call_id` to 64 characters** — the OpenAI Responses API rejects `call_id` values longer than 64 characters with a 400. Long upstream tool-call ids (some clients emit ids well over the limit) are now clamped deterministically on both the `function_call` item and its matching `function_call_output`, so the pair stays matched through the orphaned-output filter and the request is accepted. (thanks @anuragg-saxenaa, @ngapngap) -- **fix(oauth): GitHub Copilot token refresh now sends the public client_id** — the `github` provider config never carried a `clientId`, so GitHub OAuth `refresh_token` exchanges either omitted `client_id` or sent the literal string `undefined` (and a bogus `client_secret=undefined`), which GitHub rejects — leaving a Copilot connection stuck once its short-lived token expired and the long-lived refresh path was needed. The provider now resolves its public device-flow `client_id` from the embedded public credential and omits `client_secret` entirely (GitHub's Copilot app is a public client with no secret). (thanks @baslr) -- **fix(translator): a tool property named `pattern` survives Gemini/Antigravity schema sanitization** — the Gemini schema sanitizer strips JSON-Schema constraint keywords Gemini rejects (`pattern`, `minLength`, …) at every nesting level, but it also deleted any tool **property** literally _named_ one of those keywords. glob/grep tools declare a property called `pattern`, so on `ag/*` (Antigravity) backends that argument (and its `required` entry) was silently dropped, breaking the tools. Keyword stripping is now position-aware: it only removes constraint keywords at the schema-node level and never against the user-defined names inside a `properties` map. A genuine string-level `pattern` _constraint_ is still stripped. (thanks @youthanh) -- **fix(translator): MCP `namespace` tools flatten to individual functions on the Responses→Chat path** — when a Codex CLI client routes a Responses-API request to a non-Codex backend (e.g. `kr/claude-opus-4.7`), each MCP server is declared as a `namespace` tool (`{ type:"namespace", name, tools:[…] }`). The Responses→Chat translator had no `namespace` branch, so the whole group collapsed into a single empty-schema function named `mcp____` and every MCP call returned `unsupported call: mcp____`, breaking all MCP-based workflows (context7, codegraph, custom MCPs) for that combination. The translator now expands a namespace into one Chat function per sub-tool (preserving each sub-tool's name and parameters); an empty namespace yields no tools instead of a broken placeholder. The native Codex passthrough path was already correct. (thanks @V13t4nh) -- **fix(cli): the active remote-context credential wins over an ambient `OMNIROUTE_API_KEY`** — when a remote context is selected, its scoped access token now takes precedence over an `OMNIROUTE_API_KEY` present in the environment, so the connected remote is targeted as expected. ([#4364](https://github.com/diegosouzapw/OmniRoute/pull/4364)) -- **fix(cli): wire the `contexts` command into the CLI program** — the `omniroute contexts` command (list/switch saved remote contexts) was implemented but never registered, so it was unreachable; it is now wired into the CLI program. ([#4369](https://github.com/diegosouzapw/OmniRoute/pull/4369)) -- **fix(mitm): mask bare `Bearer ` header values in the Traffic Inspector** — the inspector now redacts bare `Authorization: Bearer …` values so tokens don't leak into captured traffic. ([#4358](https://github.com/diegosouzapw/OmniRoute/pull/4358)) -- **fix(pricing): price the `gpt-5.x-pro` OpenAI models + align the opencode-go discovery test** — adds pricing for the gpt-5.x-pro models so cost telemetry reports a real cost instead of zero. ([#4355](https://github.com/diegosouzapw/OmniRoute/pull/4355)) -- **fix(sse): release the reader and cancel the stream on abort/error (no more Undici pool socket leak)** — on abort or a mid-stream error the response reader is released and the stream cancelled, preventing leaked pooled sockets that degraded later requests. ([#4309](https://github.com/diegosouzapw/OmniRoute/pull/4309) — thanks @Ardem2025) -- **fix(kiro): emit an early role-only start chunk to release the stream-readiness gate** — Kiro streams now send an initial role-only chunk so the stream-readiness gate releases promptly instead of stalling. ([#4311](https://github.com/diegosouzapw/OmniRoute/pull/4311) — thanks @artickc) -- **fix(dashboard): the proxy modal stops pre-filling new scopes with an unrelated proxy** — adding a new scope assignment no longer inherits a previously-selected proxy's configuration. ([#4312](https://github.com/diegosouzapw/OmniRoute/pull/4312)) -- **fix(open-sse): inner-ai stops silently rerouting unmatched models to `models[0]`** — an unmatched model id is no longer silently served by the first available model; the lookup now returns null and the request is handled explicitly. ([#4310](https://github.com/diegosouzapw/OmniRoute/pull/4310)) -- **fix(pollinations): handle auth-required premium models (claude, gemini, midjourney)** — premium Pollinations models that require authentication are now handled correctly instead of failing. ([#4266](https://github.com/diegosouzapw/OmniRoute/pull/4266) — thanks @oyi77) -- **fix(codex): isolate the Spark quota scope** — Codex Spark usage is tracked under its own quota scope so it no longer bleeds into other Codex quotas. ([#4293](https://github.com/diegosouzapw/OmniRoute/pull/4293) — thanks @xz-dev) -- **fix(dashboard): improve the API "try it" functionality** — fixes the request path used by the dashboard's API "try it" panel. ([#4296](https://github.com/diegosouzapw/OmniRoute/pull/4296) — thanks @edrickrenan) -- **fix: polyfill `crypto.randomUUID` for non-secure contexts** — restores UUID generation when the dashboard is served over a non-secure (plain-HTTP) origin where `crypto.randomUUID` is unavailable. ([#4287](https://github.com/diegosouzapw/OmniRoute/pull/4287) — thanks @pizzav-xyz) -- **fix(proxy): allow concurrent proxy dispatcher streams** — the proxy dispatcher no longer serializes streams, so concurrent requests through a proxied connection run in parallel. ([#4288](https://github.com/diegosouzapw/OmniRoute/pull/4288) — thanks @wilsonicdev) -- **fix(build): co-locate llmlingua SLM optionals into `dist/node_modules` (postinstall)** — the optional llmlingua SLM packages are co-located into the standalone build so the compression worker can actually spawn in production. ([#4286](https://github.com/diegosouzapw/OmniRoute/pull/4286)) -- **fix(mitm): surface AgentBridge traffic in the Traffic Inspector (D4 ingest)** — AgentBridge requests now appear in the Traffic Inspector. ([#4285](https://github.com/diegosouzapw/OmniRoute/pull/4285)) -- **fix(sse): surface undici `err.cause` on dispatcher failure** — dispatcher failures now flatten the cause chain (and `AggregateError`s) into the error detail for diagnosability. ([#4281](https://github.com/diegosouzapw/OmniRoute/pull/4281)) -- **fix(cli): harden `launch`/`launch-codex` with free-claude-code patterns** — the launchers adopt the hardened launch patterns ported from free-claude-code. ([#4278](https://github.com/diegosouzapw/OmniRoute/pull/4278)) -- **fix(compression): end-to-end audit — fixes across the whole compression flow** — a sweep of the compression pipeline fixing ultra/aggressive/lossless edge cases, accessibility-anchor handling, language detection, and mode decoupling. ([#4323](https://github.com/diegosouzapw/OmniRoute/pull/4323)) +- **fix(translator): Gemini 接受 HTTP/HTTPS 图片 URL(不再静默丢弃)** — OpenAI 风格的 `image_url` 部分,当 URL 为 `http://…` 或 `https://…` 时,到达 `convertOpenAIContentToParts`(OpenAI→Gemini 请求助手)后仅输出 `console.warn` 就被丢弃,因为 Gemini 的 `inlineData` 需要 base64 编码,而该助手是同步的(无法获取并编码)。然而 Gemini 的 `Part` 模式原生支持远程 URI 的 `fileData: { fileUri }` — 模型会自行获取资源。该助手现在生成 `fileData` 部分(`mimeType: "image/*"`,在上游获取时推断)而不是丢弃,因此传递 URL(而非 data: URI)的视觉请求现在可以完整到达 Gemini。`data:` URI 仍然通过 `inlineData` 不变处理;不支持的协议(如 `ftp:`)仍然跳过。(感谢 @East-rayyy) +- **fix(security): OAuth 回调页面不再将 `code`/`state` 中继到通配符 `postMessage` 目标** — `/callback` 的 OAuth 回调在跨域打开窗口时(远程控制台 + 本地回环回调场景的容灾机制)将 `{ code, state, ... }` 发送到 `window.opener.postMessage(..., "*")`。因此,一个恶意的页面如果在已知的重定向 URI 的弹窗中打开回调 URL,就会收到 OAuth code+state,从而可以以用户身份完成 OAuth 流程。通配符容灾机制被替换为遍历固定的可信目标源允许列表(同源 + Codex 的回环助手 `localhost:1455` / `127.0.0.1:1455`);浏览器对于源不在列表中的任何打开窗口静默丢弃消息。方法 2(`BroadcastChannel`)和方法 3(`localStorage`)— 已存在于页面中 — 在 COOP 切断打开窗口关系时仍然覆盖同源父窗口。(感谢 @aeonframework) +- **fix(compliance): 启动清理现在遵循控制台数据保留设置,而非总是修剪为 7 天** — 每次重启时,`cleanupExpiredLogs()`(启动时运行)仅从 `CALL_LOG_RETENTION_DAYS` / `APP_LOG_RETENTION_DAYS` 环境变量读取保留时间,未设置时默认为 **7 天**,并在控制台驱动的 `runAutoCleanup()`(遵循配置的保留设置)运行之前就修剪了 `usage_history`(使用分析数据)。因此控制台设置的"数据保留" 90 天被静默覆盖,使用分析页面在重启后只显示最近 7 天的数据。保留时间现在遵循优先级 **显式环境变量 → 控制台 DB 设置 → 7 天默认**,按表区分(`usage_history`→`usageHistory`、`call_logs`/`proxy_logs`/`request_detail_logs`→`callLogs`、`mcp_tool_audit`→`mcpAudit`);设置环境变量的操作员仍然胜出,非 DB 部署仍然回退到它。([#4354](https://github.com/diegosouzapw/OmniRoute/issues/4354) — 感谢 @akbardwi) +- **fix(providers): bailian-coding-plan 静态回退目录与注册表匹配(10 个模型)** — 服务商-模型扫描(#4324)将四个当前的 Model Studio coding-plan 模型(`qwen3.7-plus`、`qwen3-coder-plus`、`qwen3-coder-next`、`glm-4.7`)添加到了 `bailian-coding-plan` 注册表条目中,但遗漏了 `staticModels.ts` 中的静态容灾镜像,后者仍然只列出了旧的六个。因此静态目录(在实时发现不可用时提供服务)与注册表不一致,现有的 static↔registry 一致性测试在发布分支上变红(仅在测试影响分析恰好选中它时才暴露)。静态镜像现在按注册表顺序包含全部十个模型,恢复了一致性。([#4324](https://github.com/diegosouzapw/OmniRoute/pull/4324)) +- **fix(executors): ArenaLLM 适配 LMArena 的分片 Supabase SSR 认证 Cookie** — LMArena 迁移到了 `@supabase/ssr` 分片认证 Cookie:单个 `arena-auth-prod-v1` Cookie 现在为空,实际会话被分割到 `arena-auth-prod-v1.0`、`arena-auth-prod-v1.1`……(递增)中。用户粘贴(现在为空的)单个 Cookie 时,因此发送了空会话,上游以"无效 Cookie"拒绝。LMArena 执行器现在从其分片中重建单个 Cookie——按递增数字顺序读取 `.0`、`.1`……直到缺失为止,并将它们的原始值拼接起来(`@supabase/ssr` 的 `combineChunks` 规则:纯 `join("")`,不进行 base64 解码,不进行 JSON 解析,`base64-` 前缀原样保留)——同时保留粘贴的 Cookie jar 中的其余内容。非空的单个 Cookie 仍然原样转发(向后兼容)。凭证 UX 现在指示粘贴**完整的 Cookie 请求头**并追踪 `.0`/`.1` 存储键。([#4271](https://github.com/diegosouzapw/OmniRoute/issues/4271) — 感谢 @caussao) +- **fix(compression): 为自动缓存服务商保留可缓存前缀** — OpenAI / Codex(以及 Azure-OpenAI)使用 _自动_ 前缀缓存:上游缓存请求的最长匹配前缀(系统提示 + 最早的消息),**无需**在请求体中添加任何显式的 `cache_control` 标记。缓存感知的压缩守卫仅在请求携带显式 `cache_control` 时保护该前缀,因此对于自动缓存服务商,该守卫被跳过——而在启用压缩且 `preserveSystemPrompt: false`(或使用 `aggressive`/`ultra` 等前缀压缩模式)时,它会重写系统提示 / 最早的消息,导致必然的缓存未命中,通过 OmniRoute 的令牌消耗**反而高于**直连。守卫现在将缓存服务商本身视为充分条件(仅凭 `isCachingProvider`,不依赖 `cache_control`)来跳过系统提示并降级前缀压缩模式,且 OpenAI/Codex/Azure 现在被识别为缓存服务商。压缩功能默认仍然关闭——这仅影响已启用压缩且关闭了前缀保留的操作者。([#3955](https://github.com/diegosouzapw/OmniRoute/issues/3955)) +- **fix(executors): DuckDuckGo AI Chat 使用 duckduckgo.com(修复 400 错误)** — DuckDuckGo AI Chat 执行器之前向 `https://duck.ai` 获取状态/聊天并设置 `Origin`/`Referer`,同时仍然发送 `Sec-Fetch-Site: same-origin`,因此请求的同源三元组(主机 + Origin + Referer)不一致,后端以 HTTP 400 拒绝。所有当前的 DDG 逆向工程参考——以及服务商注册表自身的 `baseUrl`——均使用 `https://duckduckgo.com`;执行器现在在状态 URL、聊天 URL、`Origin` 和 `Referer` 中一致使用它(同源请求头现在保持一致)。`x-fe-version` 抓取正则之前要求 40 位十六进制尾部,但实际提供的令牌具有 20 位十六进制尾部(例如 `serp_20250401_100419_ET-19d438eb199b2bf7c300`),因此它静默回退到硬编码的默认值;模式放宽为有界的 `{20,40}` 尾部(仍然 ReDoS 安全)。此修复解决了报告中的 DuckDuckGo 部分;独立的 Chipotle/`chipotle` 上游故障将另行跟踪。([#4037](https://github.com/diegosouzapw/OmniRoute/issues/4037) — 感谢 @daniij) +- **fix(security): 将提示注入扫描限制在前 16 KB(热路径性能优化)** — 提示注入守卫之前将每条消息/系统字符串合并到一个缓冲区中,并在每次聊天请求时对整个内容运行多个正则表达式,没有大小上限——因此 300 KB 的请求体(粘贴的代码、RAG 上下文)意味着在热路径上进行 O(body) CPU 扫描,成为并发场景下自我造成的延迟/GC 来源。两个检测调用点(`inputSanitizer.ts` 中的 `detectInjection` 和 `promptInjection.ts` 中的自定义模式扫描)现在在正则循环之前将合并文本截取到前 **16 KB**(`MAX_INJECTION_SCAN_BYTES`)。注入指令位于提示的顶部附近,因此慷慨的上限在仅扫描有界前缀的同时保留了真正的检测能力;现有的 10 MB 请求体大小上限(保护摄入)保持不变。([#3932](https://github.com/diegosouzapw/OmniRoute/issues/3932) — 感谢 @KooshaPari) +- **fix(sse): 在新 Socket 上重试直连 Socket 失败(减少 `502` 突发)** — 默认直连 undici 调度器池将 keep-alive Socket 保留最多 4 秒,但某些边缘节点(例如 `nvidia`、`opencode-zen`)在该窗口内静默关闭空闲的 keep-alive Socket,因此下一个重用池化 Socket 的请求会失败并报 `UND_ERR_SOCKET`("对方已关闭")——且是突发性的。`proxyFetch` 已经在此类瞬时错误上重试一次,但重试复用了**同一个**池化调度器,可能抓取到另一个过期 Socket,然后落到原生 fetch(也是池化的)→ 作业在速率限制队列中等待直到 30 秒超时 → `502` + 熔断器打开。重试现��使用专用的 **no-keep-alive / no-pipelining** 调度器,因此它打开一个全新的 Socket,不可能是一个已死的池化 Socket;第一次尝试仍然使用池化调度器(保留健康的 keep-alive 复用)。与 v3.8.29 的诊断功能(`describeFetchCause`,#4281)互补。([#4252](https://github.com/diegosouzapw/OmniRoute/issues/4252) — 感谢 @klimadev) +- **fix(sse): Combo 现在在遇到首个请求体特定的 400 错误时停止,而非尝试每个目标** — 检测请求体特定 400(上下文溢出 / 格式错误 / 模型访问被拒,例如"使用 Codex 与 ChatGPT 账户时不支持该模型")的 `#2101` 守卫记录了"停止 Combo"但执行了一个裸 `break`,这仅退出了内部重试循环;`executeTarget` 随后返回 `null`,外部目标循环将其视为"此目标未产生任何内容"并推进到下一个模型。因此,一个所有 N 个目标都拒绝相同请求体的 Combo 会遍历全部 N 个目标(报告中显示一个 143 模型的 Codex Combo 迭代了每个目标),浪费了上游调用和每次尝试的工作。守卫现在通过 `{ ok, 响应 }` 契约(镜像 499 客户端断开路径)将 400 上浮,使 Combo 立即解析并停止。([#4279](https://github.com/diegosouzapw/OmniRoute/issues/4279)) +- **fix(sse): 通过 Responses-API 目标的非流式 Combo 不再返回空内容** — Responses-API 目标(codex/`cx`)即使在 `流:false` 时也从上游流式传输,其终端 `响应.completed` 快照可能携带一个非空的 `output`,但缺少助手消息项(例如仅有一个 `reasoning` 项),而流式传输的 `output_text` delta 已重建了完整消息。SSE→JSON 聚合器之前优先选择终端 `output` 整体,丢弃了重建的文本 → HTTP 200 但内容为空(尤其通过 n8n 触发,n8n 默认 `流:false`)。聚合器现在在终端输出没有消息项但重建有内容时回退到重建的 delta 输出;终端快照在已经携带消息时仍然优先。([#3948](https://github.com/diegosouzapw/OmniRoute/issues/3948)) +- **fix(executors): 保留原生 Claude OAuth 的工具名称大小写(`read` 不再泄漏为 `Read`)** — 原生 Claude OAuth 流量经过一个反指纹工具名称伪装,该伪装将字面名为 `read` 的工具在线上重命名为 `Read`,并在一个不可枚举的 `_toolNameMap` 上记录反向别名,响应端使用它来恢复客户端的原始大小写。自 v3.8.27 起,执行器返回请求体的 JSON 往返副本作为 `transformedBody`,该往返丢失了不可枚举的映射——因此恢复时看到空映射,伪装的 `Read` 原样流式传输给客户端,损坏了工具名称。执行器现在将伪装映射重新附加到序列化请求体上(镜像 Antigravity 执行器),因此工具名称大小写正确往返。([#4307](https://github.com/diegosouzapw/OmniRoute/issues/4307) — 感谢 @dev-cj) +- **fix(api): 缓存命中时 `X-OmniRoute-Response-Cost` 现在报告增量成本(≈0),而非原始成本** — 在语义缓存命中时,网关在**没有**上游调用的情况下提供存储的响应,但 `X-OmniRoute-Response-Cost` 报告的是原始调用的完整成本(从缓存的 `usage` 重新计算)。因此,对 `响应-成本` 求和的计费消费者在向服务成本 ≈$0 的响应收费(且过期条目可能夸大金额)。缓存命中现在计费 `X-OmniRoute-Response-Cost: 0.0000000000`(真正的增量成本),而避免的成本通过一个新的 **`X-OmniRoute-Cost-Saved`** 请求头暴露,用于缓存分析——镜像现有的 `tokens_saved` 概念。MISS 路径保持不变。(PRD-2026-06-19-缓存-hit-成本-reporting) +- **fix(models): 导入的视觉能力模型保留其视觉能力** — 导入服务商密钥后,视觉能力模型(例如 `architecture` 声明图像输入的 OpenRouter 模型,以及其他同步的服务商)在 `/v1/模型` 和控制台中被列为纯文本——尽管图像请求实际上可以工作。同步的模型记录从未捕获视觉标志,而目录的 OpenRouter 实时增强(从 `architecture.input_modalities` 推导视觉能力)在服务商有同步模型后会被跳过。发现过程现在在同步时捕获 `supportsVision`(从 `architecture.input_modalities`、字符串 `architecture.modality` 或顶层 `input_modalities`),镜像现有的 `supportsThinking` 捕获,且目录为同步模型展示 `capabilities.vision`。([#4264](https://github.com/diegosouzapw/OmniRoute/issues/4264) — 感谢 @FerLuisxd) +- **fix(providers): Cloudflare Workers AI 模型发现显示模型名称而非 UUID** — 导入 Cloudflare Workers AI 密钥时,模型以内部 UUID 标识符列出(例如 `429b9e8b-d99e-…`)而非其可用别名(`@cf/meta/llama-3.1-8b-instruct`)。Cloudflare 的 `/ai/模型/搜索` 返回 `{ id: "", name: "@cf/…" }`,发现过程直接将原始对象传递——因此 UUID `id` 成为了可调用的模型 ID。`cloudflare-ai` 发现现在将每个结果的 `name` → id 进行映射,展示真实的 `@cf/…` 模型 ID。([#4259](https://github.com/diegosouzapw/OmniRoute/issues/4259) — 感谢 @FerLuisxd) +- **fix(translator): 将 Responses API `call_id` 限制在 64 个字符** — OpenAI Responses API 拒绝长度超过 64 个字符的 `call_id` 值并返回 400。长上游工具调用 ID(某些客户端发出的 ID 远超此限制)现在在 `function_call` 项及其匹配的 `function_call_output` 上确定性截断,使配对通过孤立输出过滤器保持匹配且请求被接受。(感谢 @anuragg-saxenaa, @ngapngap) +- **fix(oauth): GitHub Copilot 令牌刷新现在发送公共 client_id** — `github` 服务商配置从未携带 `clientId`,因此 GitHub OAuth `refresh_token` 交换要么省略了 `client_id`,要么发送了字面字符串 `undefined`(以及伪造的 `client_secret=undefined`),GitHub 会拒绝——导致 Copilot 连接在其短期令牌过期且需要长期刷新路径时卡住。服务商现在从嵌入的公共凭证中解析其公共 device-flow `client_id`,并完全省略 `client_secret`(GitHub 的 Copilot 应用是没有 secret 的公共客户端)。(感谢 @baslr) +- **fix(translator): 名为 `pattern` 的工具属性在 Gemini/Antigravity Schema 清理后得以保留** — Gemini Schema 清理器在每个嵌套层级剥离 Gemini 拒绝的 JSON-Schema 约束关键字(`pattern`、`minLength`……),但它也删除了任何字面上以这些关键字**命名**的工具**属性**。glob/grep 工具声明了一个名为 `pattern` 的属性,因此在 `ag/*`(Antigravity)后端上,该参数(及其 `required` 条目)被静默丢弃,破坏了工具。关键字剥离现在是位置感知的:它仅在 Schema 节点级别移除约束关键字,而不会触及 `properties` 映射内的用户定义名称。真正的字符串级别 `pattern` _约束_ 仍然会被剥离。(感谢 @youthanh) +- **fix(translator): MCP `namespace` 工具在 Responses→Chat 路径上展开为单独函数** — 当 Codex CLI 客户端将 Responses-API 请求路由到非 Codex 后端(例如 `kr/claude-opus-4.7`)时,每个 MCP 服务器被声明为一个 `namespace` 工具(`{ type:"namespace", name, tools:[…] }`)。Responses→Chat 翻译器没有 `namespace` 分支,因此整个组折叠为一个名为 `mcp____` 的空 Schema 函数,每次 MCP 调用都返回 `unsupported call: mcp____`,破坏了该组合的所有基于 MCP 的工作流(context7、codegraph、自定义 MCP)。翻译器现在将命名空间展开为每个子工具一个 Chat 函数(保留每个子工具的名称和参数);空命名空间不产生任何工具而非产生一个损坏的占位符。原生 Codex 直通路径此前已经正确。(感谢 @V13t4nh) +- **fix(cli): 活跃的远程上下文凭证优先于环境中的 `OMNIROUTE_API_KEY`** — 当选择了远程上下文时,其范围限定的访问令牌现在优先于环境中存在的 `OMNIROUTE_API_KEY`,使连接的远程目标按预期被选中。([#4364](https://github.com/diegosouzapw/OmniRoute/pull/4364)) +- **fix(cli): 将 `contexts` 命令接入 CLI 程序** — `omniroute contexts` 命令(列出/切换已保存的远程上下文)已实现但从未注册,因此无法访问;现已接入 CLI 程序。([#4369](https://github.com/diegosouzapw/OmniRoute/pull/4369)) +- **fix(mitm): 在流量检查器中屏蔽裸 `Bearer ` 请求头值** — 检查器现在遮盖裸 `Authorization: Bearer …` 值,防止令牌泄漏到捕获的流量中。([#4358](https://github.com/diegosouzapw/OmniRoute/pull/4358)) +- **fix(pricing): 为 `gpt-5.x-pro` OpenAI 模型定价 + 对齐 opencode-go 发现测试** — 为 gpt-5.x-pro 模型添加定价,使成本遥测报告真实成本而非零。([#4355](https://github.com/diegosouzapw/OmniRoute/pull/4355)) +- **fix(sse): 在中止/错误时释放读取器并取消流(不再有 Undici 池 Socket 泄漏)** — 在中止或流中错误时,响应读取器被释放且流被取消,防止泄漏的池化 Socket 降低后续请求的性能。([#4309](https://github.com/diegosouzapw/OmniRoute/pull/4309) — 感谢 @Ardem2025) +- **fix(kiro): 发送早期的仅角色起始块以释放流就绪门** — Kiro 流现在发送初始的仅角色块,使流就绪门及时释放而非停滞。([#4311](https://github.com/diegosouzapw/OmniRoute/pull/4311) — 感谢 @artickc) +- **fix(dashboard): 代理模态框停止将新范围预填充为无关代理** — 添加新范围分配不再继承之前选中的代理的配置。([#4312](https://github.com/diegosouzapw/OmniRoute/pull/4312)) +- **fix(open-sse): inner-ai 停止将未匹配模型静默重路由到 `models[0]`** — 未匹配的模型 ID 不再由第一个可用模型静默提供服务;查找现在返回 null,请求被显式处理。([#4310](https://github.com/diegosouzapw/OmniRoute/pull/4310)) +- **fix(pollinations): 处理需要认证的高级模型(claude、gemini、midjourney)** — 需要认证的高级 Pollinations 模型现在正确处理而非失败。([#4266](https://github.com/diegosouzapw/OmniRoute/pull/4266) — 感谢 @oyi77) +- **fix(codex): 隔离 Spark 配额范围** — Codex Spark 使用量现在在自己的配额范围内跟踪,不再混入其他 Codex 配额。([#4293](https://github.com/diegosouzapw/OmniRoute/pull/4293) — 感谢 @xz-dev) +- **fix(dashboard): 改进 API"试用"功能** — 修复控制台 API"试用"面板使用的请求路径。([#4296](https://github.com/diegosouzapw/OmniRoute/pull/4296) — 感谢 @edrickrenan) +- **fix: 为非安全上下文 polyfill `crypto.randomUUID`** — 当控制台通过非安全(纯 HTTP)来源提供服务且 `crypto.randomUUID` 不可用时,恢复 UUID 生成。([#4287](https://github.com/diegosouzapw/OmniRoute/pull/4287) — 感谢 @pizzav-xyz) +- **fix(proxy): 允许并发的代理调度器流** — 代理调度器不再序列化流,使通过代理连接的并发请求可以并行运行。([#4288](https://github.com/diegosouzapw/OmniRoute/pull/4288) — 感谢 @wilsonicdev) +- **fix(build): 将 llmlingua SLM 可选包共置到 `dist/node_modules`(postinstall)** — 可选的 llmlingua SLM 包被共置到独立构建中,使压缩 worker 可以在生产环境中实际启动。([#4286](https://github.com/diegosouzapw/OmniRoute/pull/4286)) +- **fix(mitm): 在流量检查器中展示 AgentBridge 流量(D4 摄取)** — AgentBridge 请求现在出现在流量检查器中。([#4285](https://github.com/diegosouzapw/OmniRoute/pull/4285)) +- **fix(sse): 在调度器失败时展示 undici `err.cause`** — 调度器失败现在将原因链(和 `AggregateError`)展平到错误详情中以供诊断。([#4281](https://github.com/diegosouzapw/OmniRoute/pull/4281)) +- **fix(cli): 使用 free-claude-code 模式加固 `launch`/`launch-codex`** — 启动器采用了从 free-claude-code 移植的加固启动模式。([#4278](https://github.com/diegosouzapw/OmniRoute/pull/4278)) +- **fix(compression): 端到端审计 — 修复整个压缩流程** — 对压缩管线进行全面扫描,修复 ultra/aggressive/lossless 边缘情况、无障碍锚点处理、语言检测和模式解耦。([#4323](https://github.com/diegosouzapw/OmniRoute/pull/4323)) -### 🧪 Tests +### 🧪 测试 -- **test: align two tests left red by merged PRs** — re-aligns the db-rules classification count (#4335) and the LMArena split-cookie metadata test (#4271) after concurrent merges. ([#4346](https://github.com/diegosouzapw/OmniRoute/pull/4346)) -- **test(ci): reconcile the release/v3.8.30 baseline + test drift** — reconciles quality baselines and drifted tests accumulated on the release branch. ([#4276](https://github.com/diegosouzapw/OmniRoute/pull/4276)) +- **test: 对齐被合并 PR 遗留的两个红色测试** — 在并发合并后重新对齐 db-rules 分类计数(#4335)和 LMArena 分片 Cookie 元数据测试(#4271)。([#4346](https://github.com/diegosouzapw/OmniRoute/pull/4346)) +- **test(ci): 协调 release/v3.8.30 基线 + 测试漂移** — 协调发布分支上累积的质量基线和漂移测试。([#4276](https://github.com/diegosouzapw/OmniRoute/pull/4276)) -### 📝 Maintenance +### 📝 维护 -- **refactor(combo): `ComboContext` + extract `phaseComboSetup` (god-file split, phase 1)** — begins decomposing the combo god-file by extracting combo setup into a context object, without touching dispatch/semaphore logic. ([#4326](https://github.com/diegosouzapw/OmniRoute/pull/4326)) -- **feat(quality): cap test-file size — anti-reinflation Layer 1** — freezes the existing god-tests and caps new test files at 800 lines to stop re-inflation. ([#4273](https://github.com/diegosouzapw/OmniRoute/pull/4273)) -- **feat(quality): seed per-module mutationScore floors + a blocking aggregation ratchet (T3)** — adds per-module mutation-score floors with a blocking aggregate gate. ([#4305](https://github.com/diegosouzapw/OmniRoute/pull/4305)) -- **feat(quality): make the a11y gate real (`@axe-core/playwright` in nightly)** — wires the previously-phantom accessibility gate into the nightly run with real baselines. ([#4321](https://github.com/diegosouzapw/OmniRoute/pull/4321)) -- **feat(quality): unblock R1 — test-redundancy measurement via `disableBail`** — enables the test-redundancy measurement that was previously blocked by fail-fast. ([#4322](https://github.com/diegosouzapw/OmniRoute/pull/4322)) -- **fix(quality): the complexity gate now covers `bin/` + `electron/`, and tracked-artifacts runs in pre-commit** — extends the complexity gate's scope and moves the tracked-artifacts check into the pre-commit hook. ([#4318](https://github.com/diegosouzapw/OmniRoute/pull/4318)) -- **fix(quality): restore release/v3.8.30 green — 3 latent reds from concurrent merges** — fixes three latent test reds surfaced by concurrent merges into the release branch. ([#4335](https://github.com/diegosouzapw/OmniRoute/pull/4335)) -- **fix(combo): keep `phaseComboSetup` under the complexity ceiling** — extracts a helper so the new combo setup phase stays under the complexity gate. ([#4338](https://github.com/diegosouzapw/OmniRoute/pull/4338)) -- **ci(mutation): split over-budget batches by range/pair so every batch fits the job cap** — re-splits the mutation batches so each fits the CI job budget. ([#4272](https://github.com/diegosouzapw/OmniRoute/pull/4272)) -- **chore(ci): align the electron audit gate to the root advisory policy** — the electron-workspace audit gate now follows the same advisory policy as the root. ([#4275](https://github.com/diegosouzapw/OmniRoute/pull/4275)) -- **chore(quality): reconcile the complexity/quality baselines across concurrent-merge drift** — rolls up the cycle's baseline reconciliations driven by concurrent merges into the release branch. ([#4330](https://github.com/diegosouzapw/OmniRoute/pull/4330), [#4336](https://github.com/diegosouzapw/OmniRoute/pull/4336), [#4370](https://github.com/diegosouzapw/OmniRoute/pull/4370)) -- **docs: ban AI-generation footers in commits/PRs/CHANGELOG (Hard Rule #16)** — codifies the prohibition on AI-generation footers and bot co-author trailers. ([#4328](https://github.com/diegosouzapw/OmniRoute/pull/4328)) -- **docs(design): add the OmniRoute design system and visual identity specification** — adds the design-system / visual-identity specification document. (thanks @diegosouzapw) +- **refactor(combo): `ComboContext` + 提取 `phaseComboSetup`(巨文件拆分,阶段 1)** — 开始分解 Combo 巨文件,将 Combo 设置提取到上下文对象中,不触及调度/信号量逻辑。([#4326](https://github.com/diegosouzapw/OmniRoute/pull/4326)) +- **feat(quality): 限制测试文件大小 — 反膨胀第 1 层** — 冻结现有巨测试并将新测试文件限制在 800 行以内以阻止重新膨胀。([#4273](https://github.com/diegosouzapw/OmniRoute/pull/4273)) +- **feat(quality): 设定每个模块的 mutationScore 下限 + 阻塞性聚合棘轮(T3)** — 添加每个模块的变异分数下限和阻塞性聚合门禁。([#4305](https://github.com/diegosouzapw/OmniRoute/pull/4305)) +- **feat(quality): 使 a11y 门禁真正生效(`@axe-core/playwright` 加入夜间运行)** — 将之前虚设的无障碍门禁接入夜间运行并使用真实基线。([#4321](https://github.com/diegosouzapw/OmniRoute/pull/4321)) +- **feat(quality): 解除 R1 阻塞 — 通过 `disableBail` 进行测试冗余度测量** — 启用之前被 fail-fast 阻塞的测试冗余度测量。([#4322](https://github.com/diegosouzapw/OmniRoute/pull/4322)) +- **fix(quality): 复杂度门禁现在覆盖 `bin/` + `electron/`,且 tracked-artifacts 在 pre-commit 中运行** — 扩展复杂度门禁的范围并将 tracked-artifacts 检查移入 pre-commit 钩子。([#4318](https://github.com/diegosouzapw/OmniRoute/pull/4318)) +- **fix(quality): 恢复 release/v3.8.30 绿色 — 3 个来自并发合并的潜在红色** — 修复三个因并发合并到发布分支而暴露的潜在测试红色。([#4335](https://github.com/diegosouzapw/OmniRoute/pull/4335)) +- **fix(combo): 保持 `phaseComboSetup` 在复杂度上限以下** — 提取辅助函数使新的 Combo 设置阶段保持在复杂度门禁以下。([#4338](https://github.com/diegosouzapw/OmniRoute/pull/4338)) +- **ci(mutation): 按范围/配对拆分超预算批次,使每个批次符合作业上限** — 重新拆分变异批次使每个批次符合 CI 作业预算。([#4272](https://github.com/diegosouzapw/OmniRoute/pull/4272)) +- **chore(ci): 将 electron 审计门禁对齐到根 advisory 策略** — electron 工作区审计门禁现在遵循与根相同的 advisory 策略。([#4275](https://github.com/diegosouzapw/OmniRoute/pull/4275)) +- **chore(quality): 协调并发合并漂移下的复杂度/质量基线** — 汇总该周期由并发合并驱动的基线协调,合并到发布分支。([#4330](https://github.com/diegosouzapw/OmniRoute/pull/4330), [#4336](https://github.com/diegosouzapw/OmniRoute/pull/4336), [#4370](https://github.com/diegosouzapw/OmniRoute/pull/4370)) +- **docs: 禁止在提交/PR/CHANGELOG 中使用 AI 生成页脚(硬规则 #16)** — 将禁止 AI 生成页脚和机器人共同作者尾注的规定编入规则。([#4328](https://github.com/diegosouzapw/OmniRoute/pull/4328)) +- **docs(design): 添加 OmniRoute 设计系统和视觉标识规范** — 添加设计系统 / 视觉标识规范文档。(感谢 @diegosouzapw) -### 🔒 Security +### 🔒 安全 -- **fix(sse): harden the DuckDuckGo lite scraper sanitization (CodeQL)** — closes four HIGH CodeQL alerts in the no-key web-search scraper: `decodeEntities` now resolves `&` **last** so an already-escaped entity (e.g. `&lt;`) survives as literal text instead of being double-unescaped (`js/double-escaping`); `stripTags` decodes entities first, then strips tags in a loop to a fixpoint and drops any trailing unclosed `<…`, so entity-encoded markup like `<script>` can never reach the LLM/client as a live tag (`js/incomplete-multi-character-sanitization`); and the host checks in the search tests use `new URL().hostname` equality instead of substring `.includes` (`js/incomplete-url-substring-sanitization`). ([#4356](https://github.com/diegosouzapw/OmniRoute/pull/4356)) +- **fix(sse): 加固 DuckDuckGo lite 抓取器清理逻辑(CodeQL)** — 关闭无密钥网页搜索抓取器中的四个 HIGH 级别 CodeQL 告警:`decodeEntities` 现在**最后**解析 `&`,使已转义的实体(例如 `&lt;`)作为字面文本保留而非被双重反转义(`js/double-escaping`);`stripTags` 先解码实体,然后循环剥离标签直至不动点并丢弃任何尾部未闭合的 `<…`,使实体编码的标记如 `<script>` 永远无法以活动标签形式到达 LLM/客户端(`js/incomplete-multi-character-sanitization`);搜索测试中的主机检查使用 `new URL().hostname` 相等性而非子字符串 `.includes`(`js/incomplete-url-substring-sanitization`)。([#4356](https://github.com/diegosouzapw/OmniRoute/pull/4356)) -### 🔧 Dependencies +### 🔧 依赖 -- **fix(deps): bump undici to 7.28.0 and dompurify to 3.4.11 (security)** — addresses the undici SOCKS5-TLS / cache advisories and the dompurify advisory. ([#4306](https://github.com/diegosouzapw/OmniRoute/pull/4306)) -- **chore(deps): bump actions/checkout from 4 to 7** — CI checkout-action update. ([#4297](https://github.com/diegosouzapw/OmniRoute/pull/4297)) -- **fix(executors): strip `stream_options` for qwen non-streaming / thinking-mode Claude Code requests** — Claude-Code-compatible providers force the executor-level `stream` flag on via `upstreamStream = stream || isClaudeCodeCompatible` (`open-sse/handlers/chatCore.ts`), but the outgoing body keeps the caller's original `stream: false`. The shared `stream && targetFormat === "openai"` branch in `DefaultExecutor.transformRequest` then injected `stream_options: { include_usage: true }` onto a body that still said `stream: false`, and qwen upstream rejected it with `400 "'stream_options' only set this when you set stream: true"`. Same rejection when the body carries `thinking` / `enable_thinking`. The qwen branch now skips the injection (and strips any client-sent `stream_options`) when the body explicitly says `stream: false` or requests thinking, leaving regular qwen streaming requests with the usage injection intact. (thanks @anuragg-saxenaa) +- **fix(deps): 将 undici 升级到 7.28.0,dompurify 升级到 3.4.11(安全)** — 解决 undici SOCKS5-TLS / 缓存安全公告和 dompurify 安全公告。([#4306](https://github.com/diegosouzapw/OmniRoute/pull/4306)) +- **chore(deps): 将 actions/checkout 从 4 升级到 7** — CI checkout-action 更新。([#4297](https://github.com/diegosouzapw/OmniRoute/pull/4297)) +- **fix(executors): 为 qwen 非流式 / 思考模式 Claude Code 请求剥离 `stream_options`** — Claude-Code 兼容服务商通过 `upstreamStream = stream || isClaudeCodeCompatible`(`open-sse/handlers/chatCore.ts`)强制开启执行器级别的 `stream` 标志,但传出请求体保留调用者原始的 `stream: false`。`DefaultExecutor.transformRequest` 中共享的 `stream && targetFormat === "openai"` 分支随后将 `stream_options: { include_usage: true }` 注入到一个仍然声明 `stream: false` 的请求体上,qwen 上游以 `400 "'stream_options' only set this when you set stream: true"` 拒绝。当请求体携带 `thinking` / `enable_thinking` 时出现相同的拒绝。qwen 分支现在在请求体显式声明 `stream: false` 或请求思考模式时跳过注入(并剥离任何客户端发送的 `stream_options`),使常规 qwen 流式请求的 usage 注入保持不变。(感谢 @anuragg-saxenaa) --- ## [3.8.29] — 2026-06-19 -### ✨ New Features +### ✨ 新功能 -- **feat(cloud-agent): Cursor Cloud Agent via the official API-key REST API (no IDE-OAuth ban risk)** — adds a `cursor-cloud` cloud agent that drives Cursor's Background / Cloud Agents through the official REST API (`api.cursor.com`) authenticated with a user or service-account API key — the safer, first-party alternative to re-using the Cursor IDE's OAuth session (the existing `cursor` provider, which carries a ban-risk warning). Implemented as a plain REST adapter mirroring the Devin/Jules agents (`createTask`/`getStatus`/`sendMessage`/`listSources`), so it does **not** pull in the `@cursor/sdk` package and its per-platform native binaries (Cursor's SDK is itself a thin wrapper over this REST API). Cursor's UPPERCASE status enums (`CREATING`/`RUNNING`/`FINISHED`/`ERROR`) are mapped explicitly to the shared `CloudAgentStatus`, and `baseUrl` is overridable per-credential. Credentials are stored encrypted via the existing `cloud_agent_credentials` table; no schema change. ([#4227](https://github.com/diegosouzapw/OmniRoute/issues/4227) — thanks @MRDGH2821) -- **feat(routing): OpenRouter-style `auto/:` combos** — auto-routing now understands suffixed combos that separate the _category_ (what kind of route) from the _tier_ (how to optimize): `auto/coding:fast`, `auto/coding:cheap` (alias `:floor`), `auto/coding:free`, `auto/coding:pro`, `auto/coding:reliable`, plus the new category roots `auto/reasoning`, `auto/vision`, `auto/multimodal`. The **tier** picks the scoring weights — `:fast` → ship-fast, `:cheap`/`:floor` → cost-saver, `:reliable` → a new reliability-first pack (circuit-breaker health + latency stability) — while `:free`/`:pro` filter the candidate pool by model tier (`classifyTier`: free-tier vs. premium models). The **category** filters the pool by capability (`vision`/`multimodal` → vision-capable models, `reasoning` → reasoning/thinking models). Any valid `auto/:` resolves on demand; a curated set is advertised in `/v1/models` and the dashboard. Filtering is fail-open — if a constraint matches no connected models the full pool is used so routing never breaks. All composition lives in the new `open-sse/services/autoCombo/suffixComposition.ts`; the core combo scorer (`combo.ts`) is untouched. Second slice of #4235 (premium account-tier weighting is a later follow-up). ([#4235](https://github.com/diegosouzapw/OmniRoute/issues/4235) — thanks @MRDGH2821) -- **feat(routing): advertise the `auto/cheap`, `auto/offline`, `auto/smart` combos (catalog ↔ README sync)** — the README lists `auto/cheap` (cheapest-per-token first), `auto/offline` (most quota/rate-limit headroom first) and `auto/smart` (quality-first + 10% exploration), and they already resolved at request time via `parseAutoPrefix` → `createVirtualAutoCombo`. But they were missing from `AUTO_TEMPLATE_VARIANTS`, so `/v1/models` and the dashboard combos list (which iterate that catalog) never showed them — the catalog drifted from the docs (visible in the issue's screenshots). Added the three entries so they're advertised everywhere alongside the other built-in `auto/*` combos. First slice of #4235 (OpenRouter-style `auto/:` suffixes + new categories follow). ([#4235](https://github.com/diegosouzapw/OmniRoute/issues/4235) — thanks @MRDGH2821) -- **feat(cli): remote mode — drive a remote OmniRoute with scoped access tokens** — a new CLI mode that connects to a remote OmniRoute instance using scoped access tokens, so a local CLI can drive a server you don't own a session on. ([#4256](https://github.com/diegosouzapw/OmniRoute/pull/4256)) -- **feat(api): cost-telemetry parity — `X-OmniRoute-*` headers on every endpoint + a non-token cost engine** — every endpoint now emits the `X-OmniRoute-*` cost/usage headers, backed by a cost engine that also prices non-token (media/request-based) usage. ([#4247](https://github.com/diegosouzapw/OmniRoute/pull/4247)) -- **feat(api): register Kimi K2.7 Code models (`kimi-k2.7-code` + `-highspeed`)** — the new Moonshot thinking-only coding models are registered (fixed sampling; `temperature`/`top_p` marked unsupported). ([#4183](https://github.com/diegosouzapw/OmniRoute/pull/4183)) -- **feat(catalog): add `kimi-k2.7-code` to the kmca catalog + qwen-web models discovery** — surfaces the new Kimi coding model in the kmca catalog and wires qwen-web into model discovery. ([#4185](https://github.com/diegosouzapw/OmniRoute/pull/4185)) -- **feat(api): expand the `zai` provider catalog with GLM-5.2 / GLM-4.7** — adds the real GLM-5.2, GLM-4.7 and GLM-4.7-flash model ids to the Anthropic-direct `zai` provider. ([#4201](https://github.com/diegosouzapw/OmniRoute/pull/4201)) -- **feat(api): no-thinking gateway model IDs (FCC port, Fase 8.1)** — gateway model id variants that force thinking off, ported from free-claude-code. ([#4145](https://github.com/diegosouzapw/OmniRoute/pull/4145)) -- **feat(sse): mid-stream continuation for truncated streams (FCC port, Task 4.4)** — when a stream is cut short, OmniRoute can transparently continue it, ported from free-claude-code. ([#4147](https://github.com/diegosouzapw/OmniRoute/pull/4147)) -- **feat(sse): per-provider sliding-window rate-limit fallback (FCC port, Fase 8.2)** — a per-provider sliding-window rate limiter as a fallback path, ported from free-claude-code. ([#4146](https://github.com/diegosouzapw/OmniRoute/pull/4146)) -- **feat(sse): transparent stream recovery (FCC port, Fase 4, opt-in)** — opt-in transparent recovery of interrupted upstream streams, ported from free-claude-code. ([#4131](https://github.com/diegosouzapw/OmniRoute/pull/4131)) -- **feat(search): free DuckDuckGo web search as a last-resort provider (FCC port, Fase 6)** — adds a no-key DuckDuckGo web-search provider used as a last resort, ported from free-claude-code. ([#4136](https://github.com/diegosouzapw/OmniRoute/pull/4136)) -- **feat(logging): credential-redaction safety net in the pino logger (FCC port, Fase 8.3)** — a logger-level redaction pass that scrubs credentials from log output, ported from free-claude-code. ([#4140](https://github.com/diegosouzapw/OmniRoute/pull/4140)) -- **feat(memory): opt-in Qdrant scalar int8 quantization (F4.4 Q1)** — opt-in int8 scalar quantization for Qdrant-backed memory vectors. ([#4187](https://github.com/diegosouzapw/OmniRoute/pull/4187)) -- **feat(memory): opt-in sqlite-vec int8 vector quantization (F4.4 Q2)** — opt-in int8 quantization for the sqlite-vec memory backend. ([#4190](https://github.com/diegosouzapw/OmniRoute/pull/4190)) -- **feat(deploy): keep optional deps on `update` (`--include=optional`)** — the in-place update path now passes `--include=optional` so native/optional packages aren't dropped on update. ([#4260](https://github.com/diegosouzapw/OmniRoute/pull/4260)) -- **feat(dashboard): unified visual identity — grid, primitives, tables, form controls (design phases 1-4)** — a sweeping design pass aligning the dashboard with the site: grid wallpaper, button/card/input primitives, theme-aware tables and form controls. ([#4122](https://github.com/diegosouzapw/OmniRoute/pull/4122)) -- **feat(dashboard): grid wallpaper on all standalone screens + fluid 4K layout** — the identity grid now backs every standalone screen and the layout scales fluidly to 4K. ([#4158](https://github.com/diegosouzapw/OmniRoute/pull/4158)) -- **feat(dashboard): make the identity grid visible + unify the focus ring on accent** — design follow-up making the grid actually visible and standardizing focus rings on the accent color. ([#4141](https://github.com/diegosouzapw/OmniRoute/pull/4141)) -- **feat(dashboard): import only free models + free-model list controls** — the model-import page can import just the free models, with controls to manage the free-model list. ([#4176](https://github.com/diegosouzapw/OmniRoute/pull/4176) — thanks @felipesartori) -- **feat(dashboard): compact grid layout for no-auth provider accounts** — a denser grid layout for provider accounts when auth is disabled. ([#4137](https://github.com/diegosouzapw/OmniRoute/pull/4137) — thanks @felipesartori) -- **feat(dashboard): derive media `serviceKinds` from the registries (surface MiniMax + the media catalog)** — `/media-providers/[kind]` now derives its service kinds from the registries instead of a hand-maintained list, surfacing ~48 previously-invisible media providers (incl. MiniMax TTS/video/music). ([#4212](https://github.com/diegosouzapw/OmniRoute/pull/4212)) -- **feat(traffic-inspector): live (in-flight) request filter (Gap 5)** — the Traffic Inspector can filter to in-flight requests as they happen. ([#4130](https://github.com/diegosouzapw/OmniRoute/pull/4130)) -- **feat(agent-bridge): maintenance & diagnostics dashboard controls** — adds maintenance and diagnostics controls for the Agent Bridge to the dashboard. ([#4127](https://github.com/diegosouzapw/OmniRoute/pull/4127)) -- **feat(mitm): TPROXY IP_TRANSPARENT native addon + conditional loader (Epic A)** — a native `IP_TRANSPARENT` addon with a conditional loader, the foundation for TPROXY capture. ([#4148](https://github.com/diegosouzapw/OmniRoute/pull/4148)) -- **feat(mitm): Fase 3 Epic A spike — TPROXY command builder** — a transactional builder for the iptables/TPROXY command set. ([#4139](https://github.com/diegosouzapw/OmniRoute/pull/4139)) -- **feat(mitm): TPROXY setup layer — transactional apply/revert (Epic A)** — applies and reverts the TPROXY routing setup transactionally. ([#4144](https://github.com/diegosouzapw/OmniRoute/pull/4144)) -- **feat(mitm): add `setSocketMark` to the TPROXY addon (anti-loop primitive)** — exposes `setSocketMark` so OmniRoute's own egress can be marked and skipped (anti-loop). ([#4160](https://github.com/diegosouzapw/OmniRoute/pull/4160)) -- **feat(mitm): TPROXY capture-mode listener + `connectMarked` (Epic A)** — the capture-mode listener plus a marked-connect primitive. ([#4169](https://github.com/diegosouzapw/OmniRoute/pull/4169)) -- **feat(mitm): dynamic per-SNI cert authority for TPROXY (TLS decrypt 1/N)** — a per-SNI on-the-fly certificate authority, the first slice of TLS decrypt. ([#4173](https://github.com/diegosouzapw/OmniRoute/pull/4173)) -- **feat(mitm): TLS-terminating capture for TPROXY (decrypt 2/N)** — terminates TLS to capture decrypted traffic. ([#4179](https://github.com/diegosouzapw/OmniRoute/pull/4179)) -- **feat(mitm): wire the TLS decrypt engine into TPROXY capture mode (decrypt 3/N)** — connects the decrypt engine to the capture-mode pipeline. ([#4200](https://github.com/diegosouzapw/OmniRoute/pull/4200)) -- **feat(mitm): TPROXY capture-mode manager (decrypt 4a/N)** — a manager coordinating the TPROXY capture lifecycle. ([#4208](https://github.com/diegosouzapw/OmniRoute/pull/4208)) -- **feat(mitm): local-only route + trust-store installer for TPROXY decrypt (4b/N)** — a loopback-only management route plus a CA trust-store installer for the decrypt CA. ([#4211](https://github.com/diegosouzapw/OmniRoute/pull/4211)) -- **feat(dashboard): TPROXY decrypt capture toggle in the Traffic Inspector (4c/N)** — a UI toggle to enable/disable decrypted capture. ([#4216](https://github.com/diegosouzapw/OmniRoute/pull/4216)) -- **feat(compression): replace the headroom tabular encoder with a vendored GCF** — swaps the tabular encoder for a vendored GCF implementation. ([#4167](https://github.com/diegosouzapw/OmniRoute/pull/4167) — thanks @blackwell-systems) -- **feat(compression): live per-engine streaming via `compression.step` (F3.3)** — streams per-engine compression progress through a `compression.step` event. ([#4217](https://github.com/diegosouzapw/OmniRoute/pull/4217)) -- **feat(compression): show an engine node for single-engine runs in the studio** — the Compression Studio now renders an engine node even when only one engine runs. ([#4210](https://github.com/diegosouzapw/OmniRoute/pull/4210)) -- **feat(compression): expose the WaterfallInspector via a Canvas/Waterfall toggle** — adds a Canvas/Waterfall view toggle that surfaces the WaterfallInspector. ([#4238](https://github.com/diegosouzapw/OmniRoute/pull/4238)) -- **feat(compression): make `mcpAccessibility` config reachable via a settings sub-route** — exposes the `mcpAccessibility` config under a dedicated settings sub-route. ([#4237](https://github.com/diegosouzapw/OmniRoute/pull/4237)) -- **feat(compression): runnable A/B benchmark CLI (F2.4)** — a CLI to run A/B compression benchmarks. ([#4220](https://github.com/diegosouzapw/OmniRoute/pull/4220)) -- **feat(compression): add a transcript loader to the replay harness** — the replay harness can now load real transcripts. ([#4246](https://github.com/diegosouzapw/OmniRoute/pull/4246)) -- **feat(compression): wire MCP tool-cardinality reduction (F4.3, opt-in)** — opt-in reduction of MCP tool-set cardinality to shrink prompts. ([#4221](https://github.com/diegosouzapw/OmniRoute/pull/4221)) -- **feat(compression): wire RTK comment-stripping config + honor `preserveDocstrings`** — RTK comment-stripping is now configurable and honors a `preserveDocstrings` flag. ([#4242](https://github.com/diegosouzapw/OmniRoute/pull/4242)) -- **feat(compression): honor the per-filter RTK `deduplicate` flag** — RTK filters now respect a per-filter `deduplicate` flag. ([#4231](https://github.com/diegosouzapw/OmniRoute/pull/4231)) -- **feat(compression): honor the registry `enabled` flag in the stacked loop** — the stacked compression loop now skips engines disabled in the registry. ([#4244](https://github.com/diegosouzapw/OmniRoute/pull/4244)) -- **feat(compression): persist RTK grouping config (unlock R5 `enableGrouping`)** — persists the RTK grouping configuration, unlocking the R5 `enableGrouping` rule. ([#4207](https://github.com/diegosouzapw/OmniRoute/pull/4207)) -- **feat(compression): wire ultra's `modelPath`/`slmFallbackToAggressive` to the LLMLingua SLM tier** — connects the ultra tier's small-language-model knobs to the LLMLingua SLM path. ([#4257](https://github.com/diegosouzapw/OmniRoute/pull/4257)) -- **feat(quality): Onda 2 mutation-gate tooling — radiography classifier (T1) + `mutationScore` ratchet (T3)** — new mutation-testing tooling: a survivor-radiography classifier and a `mutationScore` ratchet. ([#4234](https://github.com/diegosouzapw/OmniRoute/pull/4234)) -- **feat(ci): wire the F2.4 compression budget-gate ratchet** — adds a CI ratchet that gates compression budget regressions. ([#4232](https://github.com/diegosouzapw/OmniRoute/pull/4232)) +- **feat(cloud-agent): 通过官方 API 密钥 REST API 使用 Cursor Cloud Agent(无 IDE-OAuth 封禁风险)** — 新增 `cursor-cloud` 云代理,通过官方 REST API(`api.cursor.com`)驱动 Cursor 的后台/云代理,使用用户或服务账户 API 密钥进行认证 — 这是比复用 Cursor IDE 的 OAuth 会话(即现有的 `cursor` 服务商,带有封禁风险警告)更安全的第一方替代方案。实现为纯 REST 适配器,镜像 Devin/Jules 代理(`createTask`/`getStatus`/`sendMessage`/`listSources`),因此**不会**引入 `@cursor/sdk` 包及其各平台原生二进制文件(Cursor 的 SDK 本身只是此 REST API 的一个薄封装)。Cursor 的大写状态枚举(`CREATING`/`RUNNING`/`FINISHED`/`ERROR`)被显式映射到共享的 `CloudAgentStatus`,并且 `baseUrl` 可按凭证覆盖。凭证通过现有的 `cloud_agent_credentials` 表加密存储;无需 Schema 变更。([#4227](https://github.com/diegosouzapw/OmniRoute/issues/4227) — 感谢 @MRDGH2821) +- **feat(routing): OpenRouter 风格 `auto/:` 组合** — 自动路由现在支持带后缀的组合,将 _类别_(路由类型)与 _层级_(优化方式)分离:`auto/coding:fast`、`auto/coding:cheap`(别名 `:floor`)、`auto/coding:free`、`auto/coding:pro`、`auto/coding:reliable`,以及新的类别根 `auto/reasoning`、`auto/vision`、`auto/multimodal`。**层级**选择评分权重 — `:fast` → 快速发送,`:cheap`/`:floor` → 成本节省,`:reliable` → 新的可靠性优先方案(熔断器健康度 + 延迟稳定性)— 而 `:free`/`:pro` 按模型层级过滤候选池(`classifyTier`:免费层 vs. 高级模型)。**类别**按能力过滤候选池(`vision`/`multimodal` → 支持视觉的模型,`reasoning` → 推理/思考模型)。任何有效的 `auto/:` 均可按需解析;精选集合在 `/v1/模型` 和控制台中展示。过滤采用故障开放策略 — 如果约束条件未匹配到任何已连接的模型,则使用完整候选池,确保路由永不中断。所有组合逻辑位于新的 `open-sse/services/autoCombo/suffixComposition.ts` 中;核心 Combo 评分器(`Combo.ts`)保持不变。#4235 的第二部分(高级账户层级权重为后续跟进)。([#4235](https://github.com/diegosouzapw/OmniRoute/issues/4235) — 感谢 @MRDGH2821) +- **feat(routing): 展示 `auto/cheap`、`auto/offline`、`auto/smart` 组合(目录 ↔ README 同步)** — README 中列出了 `auto/cheap`(每令牌最便宜优先)、`auto/offline`(配额/速率限制余量最多优先)和 `auto/smart`(质量优先 + 10% 探索),它们在请求时已通过 `parseAutoPrefix` → `createVirtualAutoCombo` 正常解析。但它们之前缺失于 `AUTO_TEMPLATE_VARIANTS`,因此 `/v1/模型` 和控制台组合列表(遍历该目录)从未显示它们 — 目录与文档产生偏差(可在 issue 截图中看到)。新增这三个条目,使其与其它内置 `auto/*` 组合一起在所有位置展示。#4235 的第一部分(OpenRouter 风格 `auto/:` 后缀 + 新类别随后跟进)。([#4235](https://github.com/diegosouzapw/OmniRoute/issues/4235) — 感谢 @MRDGH2821) +- **feat(cli): 远程模式 — 使用作用域访问令牌驱动远程 OmniRoute** — 新增 CLI 模式,使用作用域访问令牌连接到远程 OmniRoute 实例,使本地 CLI 可以驱动你没有会话的服务器。([#4256](https://github.com/diegosouzapw/OmniRoute/pull/4256)) +- **feat(api): 成本遥测对齐 — 每个端点均输出 `X-OmniRoute-*` 请求头 + 非令牌成本引擎** — 每个端点现在均输出 `X-OmniRoute-*` 成本/用量请求头,由同样对非令牌(媒体/基于请求的)用量进行计价的成本引擎提供支持。([#4247](https://github.com/diegosouzapw/OmniRoute/pull/4247)) +- **feat(api): 注册 Kimi K2.7 Code 模型(`kimi-k2.7-code` + `-highspeed`)** — 新的 Moonshot 纯思考编码模型已注册(固定采样;`temperature`/`top_p` 标记为不支持)。([#4183](https://github.com/diegosouzapw/OmniRoute/pull/4183)) +- **feat(catalog): 将 `kimi-k2.7-code` 添加到 kmca 目录 + qwen-web 模型发现** — 在 kmca 目录中展示新的 Kimi 编码模型,并将 qwen-web 接入模型发现。([#4185](https://github.com/diegosouzapw/OmniRoute/pull/4185)) +- **feat(api): 扩展 `zai` 服务商目录,新增 GLM-5.2 / GLM-4.7** — 将真实的 GLM-5.2、GLM-4.7 和 GLM-4.7-flash 模型 ID 添加到 Anthropic 直连的 `zai` 服务商。([#4201](https://github.com/diegosouzapw/OmniRoute/pull/4201)) +- **feat(api): 无思考网关模型 ID(FCC 移植,Fase 8.1)** — 强制关闭思考的网关模型 ID 变体,从 free-claude-code 移植。([#4145](https://github.com/diegosouzapw/OmniRoute/pull/4145)) +- **feat(sse): 截断流的中流续传(FCC 移植,Task 4.4)** — 当流被截断时,OmniRoute 可以透明地继续它,从 free-claude-code 移植。([#4147](https://github.com/diegosouzapw/OmniRoute/pull/4147)) +- **feat(sse): 每服务商滑动窗口速率限制容灾(FCC 移植,Fase 8.2)** — 每服务商滑动窗口速率限制器作为容灾路径,从 free-claude-code 移植。([#4146](https://github.com/diegosouzapw/OmniRoute/pull/4146)) +- **feat(sse): 透明流恢复(FCC 移植,Fase 4,可选启用)** — 可选启用的中断上游流透明恢复,从 free-claude-code 移植。([#4131](https://github.com/diegosouzapw/OmniRoute/pull/4131)) +- **feat(search): 免费 DuckDuckGo 网页搜索作为最后手段服务商(FCC 移植,Fase 6)** — 新增无需密钥的 DuckDuckGo 网页搜索服务商,作为最后手段使用,从 free-claude-code 移植。([#4136](https://github.com/diegosouzapw/OmniRoute/pull/4136)) +- **feat(logging): pino 日志器中的凭证脱敏安全网(FCC 移植,Fase 8.3)** — 日志器级别的脱敏处理,从日志输出中清除凭证,从 free-claude-code 移植。([#4140](https://github.com/diegosouzapw/OmniRoute/pull/4140)) +- **feat(memory): 可选启用 Qdrant 标量 int8 量化(F4.4 Q1)** — 对 Qdrant 后端记忆向量进行可选启用的 int8 标量量化。([#4187](https://github.com/diegosouzapw/OmniRoute/pull/4187)) +- **feat(memory): 可选启用 sqlite-vec int8 向量量化(F4.4 Q2)** — 对 sqlite-vec 记忆后端进行可选启用的 int8 量化。([#4190](https://github.com/diegosouzapw/OmniRoute/pull/4190)) +- **feat(deploy): `update` 时保留可选依赖(`--include=optional`)** — 原地更新路径现在传递 `--include=optional`,使原生/可选包在更新时不会被丢弃。([#4260](https://github.com/diegosouzapw/OmniRoute/pull/4260)) +- **feat(dashboard): 统一视觉标识 — 网格、基础元素、表格、表单控件(设计阶段 1-4)** — 一次全面的设计调整,使控制台与主站对齐:网格壁纸、按钮/卡片/输入基础元素、主题感知表格和表单控件。([#4122](https://github.com/diegosouzapw/OmniRoute/pull/4122)) +- **feat(dashboard): 所有独立屏幕的网格壁纸 + 流畅 4K 布局** — 标识网格现在覆盖所有独立屏幕,布局可流畅缩放到 4K。([#4158](https://github.com/diegosouzapw/OmniRoute/pull/4158)) +- **feat(dashboard): 使标识网格可见 + 统一强调色焦点环** — 设计跟进,使网格实际可见并将焦点环标准化为强调色。([#4141](https://github.com/diegosouzapw/OmniRoute/pull/4141)) +- **feat(dashboard): 仅导入免费模型 + 免费模型列表控件** — 模型导入页面可以仅导入免费模型,并提供管理免费模型列表的控件。([#4176](https://github.com/diegosouzapw/OmniRoute/pull/4176) — 感谢 @felipesartori) +- **feat(dashboard): 无认证服务商账户的紧凑网格布局** — 认证禁用时,服务商账户使用更密集的网格布局。([#4137](https://github.com/diegosouzapw/OmniRoute/pull/4137) — 感谢 @felipesartori) +- **feat(dashboard): 从注册表派生媒体 `serviceKinds`(展示 MiniMax + 媒体目录)** — `/media-providers/[kind]` 现在从注册表派生其服务类型,而非手动维护的列表,展示约 48 个之前不可见的媒体服务商(包括 MiniMax TTS/视频/音乐)。([#4212](https://github.com/diegosouzapw/OmniRoute/pull/4212)) +- **feat(traffic-inspector): 实时(进行中)请求过滤(Gap 5)** — 流量检查器现在可以过滤进行中的请求。([#4130](https://github.com/diegosouzapw/OmniRoute/pull/4130)) +- **feat(agent-bridge): 维护与诊断仪表盘控件** — 为 Agent Bridge 向控制台添加维护和诊断控件。([#4127](https://github.com/diegosouzapw/OmniRoute/pull/4127)) +- **feat(mitm): TPROXY IP_TRANSPARENT 原生插件 + 条件加载器(Epic A)** — 原生 `IP_TRANSPARENT` 插件及条件加载器,为 TPROXY 捕获奠定基础。([#4148](https://github.com/diegosouzapw/OmniRoute/pull/4148)) +- **feat(mitm): Fase 3 Epic A 探索 — TPROXY 命令构建器** — iptables/TPROXY 命令集的事务式构建器。([#4139](https://github.com/diegosouzapw/OmniRoute/pull/4139)) +- **feat(mitm): TPROXY 设置层 — 事务式应用/回滚(Epic A)** — 以事务方式应用和回滚 TPROXY 路由设置。([#4144](https://github.com/diegosouzapw/OmniRoute/pull/4144)) +- **feat(mitm): 向 TPROXY 插件添加 `setSocketMark`(反循环原语)** — 暴露 `setSocketMark`,使 OmniRoute 自身的出口流量可以被标记并跳过(反循环)。([#4160](https://github.com/diegosouzapw/OmniRoute/pull/4160)) +- **feat(mitm): TPROXY 捕获模式监听器 + `connectMarked`(Epic A)** — 捕获模式监听器加上标记连接原语。([#4169](https://github.com/diegosouzapw/OmniRoute/pull/4169)) +- **feat(mitm): 针对 TPROXY 的动态每 SNI 证书颁发机构(TLS 解密 1/N)** — 每 SNI 即时证书颁发机构,TLS 解密的第一部分。([#4173](https://github.com/diegosouzapw/OmniRoute/pull/4173)) +- **feat(mitm): 针对 TPROXY 的 TLS 终结捕获(解密 2/N)** — 终结 TLS 以捕获解密流量。([#4179](https://github.com/diegosouzapw/OmniRoute/pull/4179)) +- **feat(mitm): 将 TLS 解密引擎接入 TPROXY 捕获模式(解密 3/N)** — 将解密引擎连接到捕获模式管线。([#4200](https://github.com/diegosouzapw/OmniRoute/pull/4200)) +- **feat(mitm): TPROXY 捕获模式管理器(解密 4a/N)** — 协调 TPROXY 捕获生命周期的管理器。([#4208](https://github.com/diegosouzapw/OmniRoute/pull/4208)) +- **feat(mitm): 本地专用路由 + 信任存储安装器,用于 TPROXY 解密(4b/N)** — 仅回环的管理路由加上解密 CA 的 CA 信任存储安装器。([#4211](https://github.com/diegosouzapw/OmniRoute/pull/4211)) +- **feat(dashboard): 流量检查器中的 TPROXY 解密捕获开关(4c/N)** — 用于启用/禁用解密捕获的 UI 开关。([#4216](https://github.com/diegosouzapw/OmniRoute/pull/4216)) +- **feat(compression): 用内置 GCF 替换余量表格编码器** — 将表格编码器替换为内置 GCF 实现。([#4167](https://github.com/diegosouzapw/OmniRoute/pull/4167) — 感谢 @blackwell-systems) +- **feat(compression): 通过 `compression.step` 实现每引擎实时流式传输(F3.3)** — 通过 `压缩.step` 事件流式传输每引擎压缩进度。([#4217](https://github.com/diegosouzapw/OmniRoute/pull/4217)) +- **feat(compression): 在 Studio 中为单引擎运行显示引擎节点** — 压缩 Studio 现在即使只有一个引擎运行时也会渲染引擎节点。([#4210](https://github.com/diegosouzapw/OmniRoute/pull/4210)) +- **feat(compression): 通过 Canvas/Waterfall 开关暴露 WaterfallInspector** — 新增 Canvas/Waterfall 视图开关,展示 WaterfallInspector。([#4238](https://github.com/diegosouzapw/OmniRoute/pull/4238)) +- **feat(compression): 通过设置子路由使 `mcpAccessibility` 配置可达** — 在专用设置子路由下暴露 `mcpAccessibility` 配置。([#4237](https://github.com/diegosouzapw/OmniRoute/pull/4237)) +- **feat(compression): 可运行的 A/B 基准测试 CLI(F2.4)** — 用于运行 A/B 压缩基准测试的 CLI。([#4220](https://github.com/diegosouzapw/OmniRoute/pull/4220)) +- **feat(compression): 向回放测试工具添加转录加载器** — 回放测试工具现在可以加载真实转录。([#4246](https://github.com/diegosouzapw/OmniRoute/pull/4246)) +- **feat(compression): 接入 MCP 工具基数削减(F4.3,可选启用)** — 可选启用的 MCP 工具集基数削减,以缩小提示。([#4221](https://github.com/diegosouzapw/OmniRoute/pull/4221)) +- **feat(compression): 接入 RTK 注释剥离配置 + 遵循 `preserveDocstrings`** — RTK 注释剥离现在可配置并遵循 `preserveDocstrings` 标志。([#4242](https://github.com/diegosouzapw/OmniRoute/pull/4242)) +- **feat(compression): 遵循每过滤器 RTK `deduplicate` 标志** — RTK 过滤器现在遵循每过滤器的 `deduplicate` 标志。([#4231](https://github.com/diegosouzapw/OmniRoute/pull/4231)) +- **feat(compression): 在堆叠循环中遵循注册表 `enabled` 标志** — 堆叠压缩循环现在跳过注册表中已禁用的引擎。([#4244](https://github.com/diegosouzapw/OmniRoute/pull/4244)) +- **feat(compression): 持久化 RTK 分组配置(解锁 R5 `enableGrouping`)** — 持久化 RTK 分组配置,解锁 R5 `enableGrouping` 规则。([#4207](https://github.com/diegosouzapw/OmniRoute/pull/4207)) +- **feat(compression): 将 ultra 的 `modelPath`/`slmFallbackToAggressive` 接入 LLMLingua SLM 层** — 将 ultra 层的小语言模型旋钮连接到 LLMLingua SLM 路径。([#4257](https://github.com/diegosouzapw/OmniRoute/pull/4257)) +- **feat(quality): Onda 2 变异门工具 — 射线分类器(T1)+ `mutationScore` 棘轮(T3)** — 新的变异测试工具:幸存者射线分类器和 `mutationScore` 棘轮。([#4234](https://github.com/diegosouzapw/OmniRoute/pull/4234)) +- **feat(ci): 接入 F2.4 压缩预算门棘轮** — 新增 CI 棘轮,防止压缩预算回退。([#4232](https://github.com/diegosouzapw/OmniRoute/pull/4232)) -### 🐛 Fixed +### 🐛 问题修复 -- **fix(providers): qwen-web model discovery now lists the live catalog instead of nothing** — the `qwen-web` cookie provider had no entry in `PROVIDER_MODELS_CONFIG`, so its model-discovery page returned an empty/stale local catalog (the OAuth fallback at the top of the route only fires for `provider === "qwen"`, leaving `qwen-web` to fall through to the no-config branch). Added a `qwen-web` entry that fetches the **public** `https://chat.qwen.ai/api/v2/models` endpoint (no auth header) and parses the `{ data: { data: [{ id, name, owned_by }] } }` shape (with a flatter `{ data: [] }` fallback). This is Problem #3 of #3931 (diagnosed by @thezukiru); Problem #1 — validator bare-token false-positive — shipped earlier in #3958, and Problem #2 — empty stream from Qwen WAF bot-detection on the streaming endpoint — remains a separate upstream/stealth concern. ([#3931](https://github.com/diegosouzapw/OmniRoute/issues/3931) — thanks @thezukiru) -- **fix(providers): ZenMux model discovery now lists the live catalog (incl. the free models) instead of the stale 9-entry hardcoded list** — adding a ZenMux key validated fine, but the connection then showed `API unavailable — using local catalog` and was missing the free models ZenMux advertises (`z-ai/glm-5.2-free`, `moonshotai/kimi-k2.7-code-free`). Root cause: `zenmux` carries a correct `modelsUrl` in the registry, but — like `llm7`/`byteplus` before #3976 — it was not classified by any live-fetch branch of the model-import route (not `openai-compatible-*`, not self-hosted, not in `NAMED_OPENAI_STYLE_PROVIDERS`), so the route never probed the upstream `/models` and fell through to the registry's hardcoded `models[]`. Added `zenmux` to `NAMED_OPENAI_STYLE_PROVIDERS`, so the route probes `https://zenmux.ai/api/v1/models` (the `/chat/completions`-stripped `/models` candidate) and serves the live list, falling back to the local catalog only when the upstream fetch fails — import never breaks. ([#4202](https://github.com/diegosouzapw/OmniRoute/issues/4202) — thanks @mikmaneggahommie) -- **fix(providers): Vercel AI Gateway "import models" now loads the live catalog instead of nothing** — adding a Vercel AI Gateway key worked, but clicking **import** on the models page loaded nothing usable (manually adding the same models worked). Same class as #4202 (zenmux) / #3976 (llm7/byteplus): `vercel-ai-gateway` carries a real `baseUrl` (`https://ai-gateway.vercel.sh/v1/chat/completions`, format `openai`) in the registry, but was not classified by any live-fetch branch of the model-import route (not `openai-compatible-*`, not self-hosted, not in `NAMED_OPENAI_STYLE_PROVIDERS`), so the route never probed the upstream `/models` and fell through to the registry's tiny 5-entry hardcoded `models[]`. Added `vercel-ai-gateway` to `NAMED_OPENAI_STYLE_PROVIDERS`, so the route probes `https://ai-gateway.vercel.sh/v1/models` (the `/chat/completions`-stripped `/models` candidate) and serves the live list, falling back to the local catalog only when the upstream fetch fails — import never breaks. ([#4249](https://github.com/diegosouzapw/OmniRoute/issues/4249) — thanks @FerLuisxd) -- **fix(sse): clear error when the request queue drops a job (no more fake-upstream "This job timed out after Nms")** — under concurrent load, requests that exceed the per-connection rate-limit queue budget (`resilienceSettings.requestQueue.maxWaitMs`) were dropped by Bottleneck with its raw `This job timed out after ms.` message. That string is indistinguishable from an upstream gateway timeout, so the 502 body and call-log `last_error` looked like a provider outage across unrelated providers (TI:0\|TO:0) — an operator spent ~3h misdiagnosing local queue saturation as upstream failures. `withRateLimit` now rewrites that specific Bottleneck error into a clear, OmniRoute-owned message that names the knob (`requestQueue.maxWaitMs`, tunable in Settings → Resilience), explicitly disclaims an upstream timeout, preserves the original as `cause`, and tags `code: "RATE_LIMIT_QUEUE_TIMEOUT"`. Behavior is unchanged — the job is still dropped so combo falls back to the next target. ([#4165](https://github.com/diegosouzapw/OmniRoute/issues/4165) — thanks @KooshaPari) -- **fix(api): advertise the built-in `auto/*` combos in `/v1/models`** — OmniRoute ships a zero-setup `auto/*` catalog (`auto/best-coding`, `auto/pro-reasoning`, …, 16 variants) that the dashboard advertises and that resolve on demand, but the `/v1/models` listing only emitted persisted DB combos + provider models. Clients that build their model picker from `/v1/models` (e.g. Hermes Agent) never saw any `auto/*` option. The catalog now emits every `AUTO_TEMPLATE_VARIANTS` id (as `owned_by: "combo"`) at the top of the list, deduped against persisted combos. (Showing each `auto/*`'s dynamically-selected members is a separate enhancement.) ([#4164](https://github.com/diegosouzapw/OmniRoute/issues/4164) — thanks @MRDGH2821) -- **fix(sse): restore MCP / third-party tool names on the native Claude path (MCP dispatch broken in Claude Code)** — since 3.8.27, every MCP tool call routed through OmniRoute to a native Claude OAuth provider failed client-side with `Error: No such tool available: `: tool schemas arrived fine but the streamed `tool_use.name` reached Claude Code in its cloaked form (e.g. `McpN8nMcpSearchWorkflows` instead of the registered `mcp__n8n-mcp__search_workflows`). The native-Claude tool-name cloak stashes its per-request alias→original map as a **non-enumerable** `_toolNameMap` on the request body; the request-inspector capture added in 3.8.27 rebuilds the captured body from its serialized form (`JSON.parse(JSON.stringify(...))`), which drops non-enumerable properties, so `finalBody._toolNameMap` was empty and the response-side un-cloak silently fell back to the static built-in map — never restoring dynamic MCP / snake_case names. Built-in tools (Bash/Read/…) were unaffected (static map); cross-format paths were unaffected (they attach the map enumerably). The provider-request capture now re-attaches the per-request map (kept non-enumerable, so it still never re-serializes upstream) when the captured copy lost it, restoring MCP tool dispatch. ([#4091](https://github.com/diegosouzapw/OmniRoute/issues/4091) — thanks @pedrotecinf, @NakHalal) -- **fix(dashboard): Logs auto-refresh self-heals in embedded/proxied hosts that pin or mis-fire visibility** — a follow-up to #4054: the Request Logger still froze auto-refresh on some hosts (reported on 3.8.28 Docker, works on 3.8.24). #4054 made the initial visibility fail-open, but the pause is event-driven — a host that fires a one-shot `visibilitychange` → hidden and then keeps reporting `"hidden"` (or recovers without firing the event again) left the cached visibility flag stuck `false`, so the interval ticked but never polled (only the manual Refresh button worked). The poll tick now also re-checks the **live** `document.visibilityState`, and a **window `focus`** listener re-arms polling (a focused window is a reliable signal the page is actively viewed). A genuinely backgrounded browser tab still pauses (it reports `"hidden"` and never receives focus), preserving the #3109 network-saturation optimization. ([#4133](https://github.com/diegosouzapw/OmniRoute/issues/4133) — thanks @tjengbudi) -- **fix(capabilities): unify vision model-id detection into one shared source** — three code paths kept independent, drifting vision-model lists, so the same model id could get up to three different verdicts. Two concrete bugs: lite compression's gate was missing pixtral / llava / qwen-vl / glm-4v / kimi-vl / mistral-medium-3, so it **stripped images for those real vision models and blinded them** (same class as #4071 / #4012); and the `/v1/models` list was too broad, flagging text models (`gemma`, bare `kimi` like `kimi-k2`) as vision. All three (`modelCapabilities` routing fallback, `/v1/models` listing, lite image-strip gate) now delegate to a single conservative source `src/shared/constants/visionModels.ts`, which also restores `glm-4v` / `gemini-3` coverage and keeps the #3328 MiniMax M3 carve-out. ([#4072](https://github.com/diegosouzapw/OmniRoute/issues/4072) — thanks @diego-anselmo) -- **fix(sse): surface mid-stream Gemini errors instead of returning a truncated 200** — when an upstream Gemini SSE stream emitted some partial content and then a JSON error object (`{"error":{"code":503,"message":"…high demand…","status":"UNAVAILABLE"}}`) instead of a `candidates` payload, OmniRoute silently dropped it: the gemini→openai translator's no-candidate branch only handled `promptFeedback` (content-filter) and returned `null` for anything else, so the stream simply ended and the client got HTTP 200 with a truncated body and `finish_reason: "stop"` — masking the failure and skipping combo fallback. `geminiToOpenAIResponse` now detects an `error` object (optionally wrapped in `response`), records it as `state.upstreamError` (preserving the real status — 503/`UNAVAILABLE`, or 429 for `RESOURCE_EXHAUSTED`), and lets `stream.ts` error the stream out through the existing `onFailure`/`buildErrorBody`/`controller.error` path — the same mechanism the openai-responses translator already uses. ([#4177](https://github.com/diegosouzapw/OmniRoute/issues/4177) — thanks @hartmark) -- **fix(capabilities): resolve models.dev-synced vision metadata for Mistral `-latest` aliases** — root cause behind the #4071 heuristic: `getResolvedModelCapabilities("mistral/pixtral-12b-latest").supportsVision` resolved `null` (vision came only from the #4071 model-id heuristic, with `attachment` still `null`) even though models.dev exposes the model as multimodal. Confirmed against the live models.dev API: it catalogs Pixtral 12B under the **short** id `pixtral-12b` (with `attachment: true`, `modalities.input: ["text","image"]`), while requests use the Mistral API alias `pixtral-12b-latest`. The synced lookup tried the exact / raw / static-spec-canonical ids — all of which miss the short form — so it fell through to the heuristic. `getSyncedCapabilityForResolved` now adds a last-resort fallback that retries with a trailing `-latest` stripped, so synced metadata (`attachment` / image modalities) wins for these aliases; models whose `-latest` id is stored verbatim (e.g. `pixtral-large-latest`) keep resolving directly. Note: the models.dev sync is currently manual-only (Settings → models.dev) with no scheduled refresh, so a fresh instance still relies on the #4071 heuristic until that sync runs — a periodic-refresh cadence is left as a separate follow-up. ([#4073](https://github.com/diegosouzapw/OmniRoute/issues/4073) — thanks @diego-anselmo) -- **fix(sse): map Xiaomi MiMo reasoning control to its native `thinking:{type}` shape** — MiMo (`api.xiaomimimo.com`) controls chain-of-thought **only** via top-level `thinking:{type:"enabled"|"disabled"}` and does not understand OpenAI's `reasoning_effort`/`reasoning`, while its request validator is strict (`400 Param Incorrect`). OmniRoute's OpenAI path carried reasoning intent as `reasoning_effort`, and the claude→openai translator can leave a Claude-shaped `thinking:{type, budget_tokens}` — so the client's on/off choice was silently dropped and `budget_tokens`/`reasoning_effort` rode along as extra params the validator can reject. New `open-sse/services/mimoThinking.ts::normalizeMimoThinking` (wired in `chatCore` for `provider==="xiaomi-mimo"`) reduces any thinking object to just `{type}` (`disabled` stays; `enabled`/`adaptive`/other → `enabled`) and drops `reasoning_effort`/`reasoning`. It deliberately does **not** synthesize thinking from a bare `reasoning_effort` — `mimo-v2-omni` is non-thinking, so that could turn a silently-ignored param into a hard error. ([#4224](https://github.com/diegosouzapw/OmniRoute/pull/4224)) -- **fix(capabilities): Xiaomi MiMo `*-pro` chat models are text-only (no vision)** — only `mimo-v2.5` and `mimo-v2-omni` accept images per Xiaomi's docs; `mimo-v2.5-pro`/`mimo-v2-pro` are text-only, but `modelSpecs` marked them vision-capable and models.dev mislabels them ([hermes-agent#18884](https://github.com/NousResearch/hermes-agent/issues/18884)). Since `resolveVisionCapability` lets a synced `attachment:true` win first, an image request could be routed to a blind model (the #4071 failure mode). Corrected the specs **and** added a hard override in `resolveVisionCapability` (checked before the synced branch, anchored so `mimo-v2.5-pro` never matches the multimodal `mimo-v2.5`) that beats the wrong synced attachment. Also registered the missing native `mimo-v2-pro` chat model and the missing `mimo-v2-tts` speech model. ([#4224](https://github.com/diegosouzapw/OmniRoute/pull/4224)) -- **fix(sse): Claude Opus 4.7+/Fable 5 use adaptive thinking only (no more manual-budget 400s)** — Opus 4.7 and later (Opus 4.7/4.8, Fable 5) removed manual extended thinking: `thinking.type:"enabled"` or **any** `thinking.budget_tokens` now returns `400` ("Any request that tries to set a fixed thinking budget gets a 400" — Anthropic migration guide). Reasoning is adaptive-only, steered by `output_config.effort`. OmniRoute's OpenAI→Claude translator mapped `reasoning_effort` low/medium/high to a manual `thinking:{type:"enabled", budget_tokens}`, so those requests hard-400'd on the most-used provider (and a Claude-native passthrough client sending the legacy shape did too). A new `adaptiveThinkingOnly` model flag now drives two fixes: the translator maps `reasoning_effort` of **every** level to `{type:"adaptive"}` + `output_config.effort` (preserving the requested level, never a budget) for these models, and a `normalizeClaudeAdaptiveThinking` catch-all at the existing post-translation thinking-normalization chokepoint collapses any residual manual thinking (passthrough legacy shape, per-model defaults) to `{type:"adaptive"}`, keyed on the resolved upstream model so it covers every routing mode. Pre-4.7 models (Opus 4.6/4.5, Sonnet, Haiku) keep manual budgets unchanged. ([#4230](https://github.com/diegosouzapw/OmniRoute/pull/4230)) -- **fix(providers): strip non-default temperature/top_p/top_k for Claude Opus 4.7+/Fable 5 (fixed sampling → no 400)** — Opus 4.7 and later reject non-default `temperature`/`top_p`/`top_k` with a `400` (sampling is fixed; reasoning moved to `output_config.effort`). The translator forwarded client-supplied `temperature`/`top_p` unconditionally and the Claude registry models carried no `unsupportedParams`, so a plain OpenAI-format request with `temperature: 0.7` to `claude-opus-4-8` hard-400'd. Added `unsupportedParams: ["temperature","top_p","top_k"]` to the Opus 4.7+/Fable 5 ids in both the `claude` (dashed `claude-opus-4-8`) and `anthropic` (dotted `claude-opus-4.7`) registries, so they're stripped at the existing `getUnsupportedParams` dispatch chokepoint. Pre-4.7 Claude models still accept sampling params. ([#4230](https://github.com/diegosouzapw/OmniRoute/pull/4230)) -- **fix(providers): conditionally strip temperature/top_p for GPT-5 reasoning on the `openai` Chat Completions path (no 400 when an effort is active)** — GPT-5 reasoning models reject non-default `temperature`/`top_p` with a `400` whenever a reasoning effort is active, yet accept them again under `reasoning_effort:"none"` (the GPT-5.1+ default, i.e. non-reasoning mode). On the `openai` provider only `o3` carried `REASONING_UNSUPPORTED`; `gpt-5.5`/`gpt-5.4`/`gpt-5.4-mini`/`gpt-5.4-nano` carried no sampling guard, so a `temperature` + active-effort request hard-400'd. A static `unsupportedParams` list can't express the `none`-mode carve-out (it would over-strip the legitimate case), so the new `gpt5SamplingGuard` drops `temperature`/`top_p` only when the resolved effort is active — wired at the existing `getUnsupportedParams` chokepoint and scoped to the `openai` Chat Completions surface (the `codex` Responses path is already covered by the CodexExecutor allowlist; other providers are untouched). ([#4245](https://github.com/diegosouzapw/OmniRoute/pull/4245)) -- **fix(codex): stop silently dropping GPT-5 output verbosity (`verbosity` / `text.verbosity`)** — the GPT-5 series added an output-verbosity control: `verbosity` (low/medium/high) on Chat Completions, nested as `text.verbosity` on the Responses API. The CodexExecutor gates translated requests through an allowlist that had no `text` entry, so for the `codex` provider the hint was dropped before reaching upstream (the `openai` Chat path already forwarded it). `normalizeCodexVerbosity` now folds whichever shape arrived into a single validated `text:{verbosity}` before the allowlist (which now permits `text`), and the OpenAI Chat↔Responses request translators map `verbosity` across formats so the hint survives a format crossing for non-codex Responses backends too. Invalid/absent verbosity collapses to no `text` (status quo). ([#4245](https://github.com/diegosouzapw/OmniRoute/pull/4245)) -- **fix(sse): map `reasoning_effort` to DeepSeek V4's native `{high, max}` vocabulary** — DeepSeek V4 only understands `high`/`max` reasoning levels, so other `reasoning_effort` values are mapped onto its native vocabulary instead of being rejected. ([#4219](https://github.com/diegosouzapw/OmniRoute/pull/4219)) -- **fix(glm): default `max_tokens` and an extended timeout for GLM-5.2+ thinking** — GLM-5.2+ thinking responses are slow and need headroom, so OmniRoute now sets a sensible default `max_tokens` and a longer timeout for them. ([#4255](https://github.com/diegosouzapw/OmniRoute/pull/4255) — thanks @dhaern) -- **fix(antigravity): default `includeThoughts` for modern Gemini models** — modern Gemini models on the Antigravity path now default to including thoughts so reasoning isn't silently dropped. ([#4180](https://github.com/diegosouzapw/OmniRoute/pull/4180) — thanks @dhaern) -- **fix(provider-registry): add correct `contextLength` to theoldllm models** — fills in accurate context-window sizes for theoldllm's models. ([#4184](https://github.com/diegosouzapw/OmniRoute/pull/4184) — thanks @herjarsa) -- **fix(models): expose combo model token limits** — `/v1/models` now reports token limits for combo models. ([#4189](https://github.com/diegosouzapw/OmniRoute/pull/4189) — thanks @megamen32) -- **fix(combo): keep the passthrough quota fallback scoped** — prevents the passthrough quota fallback from leaking across unrelated targets. ([#4194](https://github.com/diegosouzapw/OmniRoute/pull/4194) — thanks @Svetznaniy33) -- **fix(combo): opt proactive-fallback compression into the TV1 bail-out (no silent target drop)** — proactive-fallback compression now participates in the TV1 bail-out so a target is never silently dropped. ([#4228](https://github.com/diegosouzapw/OmniRoute/pull/4228)) -- **fix(compression): show engine preview output** — the Compression Studio preview now renders the engine's output. ([#4128](https://github.com/diegosouzapw/OmniRoute/pull/4128) — thanks @megamen32) -- **fix(compression): harden engines against I/O failures and misconfig (F5.3)** — compression engines degrade gracefully on I/O errors and bad configuration instead of throwing. ([#4198](https://github.com/diegosouzapw/OmniRoute/pull/4198)) -- **fix(compression): harden RTK raw-output redaction + ReDoS guard for custom filters (F5.3)** — broadens RTK raw-output redaction and adds a ReDoS guard for user-supplied filter patterns. ([#4203](https://github.com/diegosouzapw/OmniRoute/pull/4203)) -- **fix(compression): bound `mcpAccessibility` `maxTextChars` on the live read path** — the live read path now clamps `maxTextChars` so a small value can't make tools disappear. ([#4206](https://github.com/diegosouzapw/OmniRoute/pull/4206)) -- **fix(dashboard): data tables paint an opaque surface so the grid doesn't bleed through** — data tables now render on an opaque surface, fixing the grid wallpaper showing through. ([#4233](https://github.com/diegosouzapw/OmniRoute/pull/4233)) -- **fix(dashboard): make the provider card hover visible (was ~1% opacity)** — the provider-card hover state was effectively invisible; it now has a visible surface. ([#4214](https://github.com/diegosouzapw/OmniRoute/pull/4214)) -- **fix(vscode): sanitize implicit editor context** — redacts sensitive filenames/keywords from the implicit VS Code editor context before it's sent upstream. ([#4124](https://github.com/diegosouzapw/OmniRoute/pull/4124) — thanks @zhiru) -- **fix(build): raise the Node heap for the local `next build` to stop OOM/stall** — bumps the build-time heap so the local production build no longer OOMs or stalls. ([#4171](https://github.com/diegosouzapw/OmniRoute/pull/4171)) -- **fix(mitm): TPROXY OUTPUT-based recipe for local traffic (validated e2e on VPS)** — switches the TPROXY rules to an OUTPUT-chain recipe so locally-originated traffic is captured; validated end-to-end on the VPS. ([#4156](https://github.com/diegosouzapw/OmniRoute/pull/4156)) -- **fix(mitm): forward anti-loop — put the bypass-marked socket on the Agent (decrypt 4d)** — places the bypass-marked socket on the HTTP Agent so OmniRoute's own forwarded traffic never re-enters the capture loop; VPS-validated. ([#4229](https://github.com/diegosouzapw/OmniRoute/pull/4229)) -- **fix(free-tiers): retire dead-tier `hasFree`, round the headline to ~1.6B, regenerate the per-provider table** — drops dead free tiers from the headline math and regenerates the per-provider free-tier table. ([#4142](https://github.com/diegosouzapw/OmniRoute/pull/4142)) -- **fix(free-tiers): retire 4 re-verified-dead free tiers, flag iflytek/sparkdesk ToS, clarify monsterapi one-time** — removes four confirmed-dead free tiers and annotates ToS/one-time caveats. ([#4152](https://github.com/diegosouzapw/OmniRoute/pull/4152)) +- **fix(providers): qwen-web 模型发现现在列出实时目录而非空目录** — `qwen-web` cookie 服务商在 `PROVIDER_MODELS_CONFIG` 中没有条目,因此其模型发现页面返回空/过时的本地目录(路由顶部的 OAuth 容灾仅对 `服务商 === "qwen"` 生效,导致 `qwen-web` 落入无配置分支)。新增一个 `qwen-web` 条目,获取**公开**的 `https://chat.qwen.ai/api/v2/模型` 端点(无需认证请求头)并解析 `{ data: { data: [{ id, name, owned_by }] } }` 结构(带更扁平的 `{ data: [] }` 容灾)。这是 #3931 的问题 #3(由 @thezukiru 诊断);问题 #1 — 校验器裸令牌误报 — 已在 #3958 中发布,问题 #2 — Qwen WAF 在流式传输端点的机器人检测导致空流 — 仍然是独立的上游/隐身问题。([#3931](https://github.com/diegosouzapw/OmniRoute/issues/3931) — 感谢 @thezukiru) +- **fix(providers): ZenMux 模型发现现在列出实时目录(含免费模型)而非过时的 9 条目硬编码列表** — 添加 ZenMux 密钥校验正常,但连接随后显示 `API unavailable — using local catalog` 并缺少 ZenMux 宣传的免费模型(`z-ai/glm-5.2-free`、`moonshotai/kimi-k2.7-code-free`)。根本原因:`zenmux` 在注册表中带有正确的 `modelsUrl`,但是 — 与 #3976 之前的 `llm7`/`byteplus` 一样 — 它未被模型导入路由的任何实时获取分支分类(不是 `openai-compatible-*`、不是自托管、不在 `NAMED_OPENAI_STYLE_PROVIDERS` 中),因此路由从未探测上游 `/模型` 并落入注册表的硬编码 `模型[]`。将 `zenmux` 添加到 `NAMED_OPENAI_STYLE_PROVIDERS`,使路由探测 `https://zenmux.ai/api/v1/模型`(去掉 `/chat/completions` 的 `/模型` 候选)并提供实时列表,仅在上游获取失败时回退到本地目录 — 导入永不中断。([#4202](https://github.com/diegosouzapw/OmniRoute/issues/4202) — 感谢 @mikmaneggahommie) +- **fix(providers): Vercel AI Gateway"导入模型"现在加载实时目录而非空目录** — 添加 Vercel AI Gateway 密钥有效,但在模型页面点击**导入**时没有加载任何可用内容(手动添加相同模型有效)。与 #4202(zenmux)/ #3976(llm7/byteplus)同类型:`vercel-ai-网关` 在注册表中带有真实的 `baseUrl`(`https://ai-网关.vercel.sh/v1/chat/completions`,格式 `openai`),但未被模型导入路由的任何实时获取分支分类(不是 `openai-compatible-*`、不是自托管、不在 `NAMED_OPENAI_STYLE_PROVIDERS` 中),因此路由从未探测上游 `/模型` 并落入注册表的 5 条目硬编码 `模型[]`。将 `vercel-ai-网关` 添加到 `NAMED_OPENAI_STYLE_PROVIDERS`,使路由探测 `https://ai-网关.vercel.sh/v1/模型`(去掉 `/chat/completions` 的 `/模型` 候选)并提供实时列表,仅在上游获取失败时回退到本地目录 — 导入永不中断。([#4249](https://github.com/diegosouzapw/OmniRoute/issues/4249) — 感谢 @FerLuisxd) +- **fix(sse): 请求队列丢弃任务时提供清晰错误(不再有伪造上游的"This job timed out after Nms")** — 在并发负载下,超出每连接速率限制队列预算(`resilienceSettings.requestQueue.maxWaitMs`)的请求被 Bottleneck 以原始 `This job timed out after ms.` 消息丢弃。该字符串与上游网关超时无法区分,因此 502 响应体和调用日志 `last_error` 看起来像是跨无关服务商的服务商宕机(TI:0\|TO:0)— 一位运维人员花了约 3 小时将本地队列饱和误诊为上游故障。`withRateLimit` 现在将该特定 Bottleneck 错误重写为一条清晰的、OmniRoute 自有消息,指明可调参数(`requestQueue.maxWaitMs`,可在 Settings → Resilience 中调整),明确否认上游超时,保留原始错误作为 `cause`,并标记 `code: "RATE_LIMIT_QUEUE_TIMEOUT"`。行为不变 — 任务仍被丢弃,Combo 回退到下一个目标。([#4165](https://github.com/diegosouzapw/OmniRoute/issues/4165) — 感谢 @KooshaPari) +- **fix(api): 在 `/v1/models` 中展示内置 `auto/*` 组合** — OmniRoute 附带零配置 `auto/*` 目录(`auto/best-coding`、`auto/pro-reasoning`、…、16 个变体),仪表盘已展示并可按需解析,但 `/v1/models` 列表仅输出持久化的 DB 组合 + 服务商模型。从 `/v1/models` 构建模型选择器的客户端(如 Hermes Agent)从未看到任何 `auto/*` 选项。目录现在在列表顶部输出每个 `AUTO_TEMPLATE_VARIANTS` ID(作为 `owned_by: "combo"`),并与持久化组合去重。(显示每个 `auto/*` 的动态选定成员是单独的增强功能。)([#4164](https://github.com/diegosouzapw/OmniRoute/issues/4164) — 感谢 @MRDGH2821) +- **fix(sse): 在原生 Claude 路径上恢复 MCP / 第三方工具名称(Claude Code 中 MCP 调度中断)** — 自 3.8.27 起,通过 OmniRoute 路由到原生 Claude OAuth 服务商的每个 MCP 工具调用都在客户端失败,报错 `Error: No such tool available: `:工具 Schema 正常到达,但流式传输的 `tool_use.name` 以其伪装形式到达 Claude Code(例如 `McpN8nMcpSearchWorkflows` 而非注册的 `mcp__n8n-mcp__search_workflows`)。原生 Claude 工具名称伪装将其每请求别名→原始映射作为**不可枚举**的 `_toolNameMap` 存储在请求体上;3.8.27 中添加的请求检查器捕获从序列化形式(`JSON.parse(JSON.stringify(...))`)重建捕获体,这会丢弃不可枚举属性,因此 `finalBody._toolNameMap` 为空,响应端反伪装静默回退到静态内置映射 — 从不恢复动态 MCP / snake_case 名称。内置工具(Bash/Read/…)不受影响(静态映射);跨格式路径不受影响(它们以可枚举方式附加映射)。服务商请求捕获现在在捕获副本丢失时重新附加每请求映射(保持不可枚举,因此仍不会重新序列化到上游),恢复 MCP 工具调度。([#4091](https://github.com/diegosouzapw/OmniRoute/issues/4091) — 感谢 @pedrotecinf, @NakHalal) +- **fix(dashboard): 日志自动刷新在嵌入/代理主机中自愈,这些主机会卡住或误触发可见性** — #4054 的后续:请求日志器在某些主机上仍然冻结自动刷新(3.8.28 Docker 上报,3.8.24 正常工作)。#4054 使初始可见性故障开放,但暂停是事件驱动的 — 主机触发一次性 `visibilitychange` → hidden 然后持续报告 `"hidden"`(或恢复但不再次触发事件),导致缓存的可见性标志卡在 `false`,因此间隔计时器在滴答但从不轮询(只有手动刷新按钮有效)。轮询滴答现在还会重新检查**实时** `document.visibilityState`,并且**窗口 `focus`** 监听器重新启用轮询(聚焦窗口是页面正在被查看的可靠信号)。真正处于后台的浏览器标签页仍然暂停(它报告 `"hidden"` 且从不接收焦点),保留 #3109 的网络饱和优化。([#4133](https://github.com/diegosouzapw/OmniRoute/issues/4133) — 感谢 @tjengbudi) +- **fix(capabilities): 将视觉模型 ID 检测统一到一个共享源** — 三个代码路径各自维护独立的、漂移的视觉模型列表,因此同一个模型 ID 可能得到最多三种不同的判定。两个具体缺陷:lite 压缩的门控缺失 pixtral / llava / qwen-vl / glm-4v / kimi-vl / mistral-medium-3,因此它**对这些真正的视觉模型剥离图像并使其致盲**(与 #4071 / #4012 同类型);而 `/v1/模型` 列表过于宽泛,将文本模型(`gemma`、裸 `kimi` 如 `kimi-k2`)标记为视觉模型。所有三个(`modelCapabilities` 路由容灾、`/v1/模型` 列表、lite 图像剥离门控)现在委托给单一保守源 `src/shared/constants/visionModels.ts`,该源还恢复了 `glm-4v` / `gemini-3` 覆盖率并保留 #3328 MiniMax M3 的例外处理。([#4072](https://github.com/diegosouzapw/OmniRoute/issues/4072) — 感谢 @diego-anselmo) +- **fix(sse): 暴露中流 Gemini 错误,而非返回截断的 200** — 当上游 Gemini SSE 流发出部分内容后发出 JSON 错误对象(`{"错误":{"code":503,"message":"…high demand…","status":"UNAVAILABLE"}}`)而非 `candidates` 载荷时,OmniRoute 静默丢弃它:gemini→openai 翻译器的无候选分支仅处理 `promptFeedback`(内容过滤器),对其他情况返回 `null`,因此流直接结束,客户端收到 HTTP 200 及截断体和 `finish_reason: "stop"` — 掩盖故障并跳过 Combo 容灾。`geminiToOpenAIResponse` 现在检测 `错误` 对象(可选地包裹在 `响应` 中),将其记录为 `state.upstreamError`(保留真实状态 — 503/`UNAVAILABLE`,或 429 对应 `RESOURCE_EXHAUSTED`),并通过现有 `onFailure`/`buildErrorBody`/`controller.错误` 路径将流出错 — 与 openai-responses 翻译器已使用的机制相同。([#4177](https://github.com/diegosouzapw/OmniRoute/issues/4177) — 感谢 @hartmark) +- **fix(capabilities): 为 Mistral `-latest` 别名解析 models.dev 同步的视觉元数据** — #4071 启发式背后的根本原因:`getResolvedModelCapabilities("mistral/pixtral-12b-latest").supportsVision` 解析为 `null`(视觉仅来自 #4071 模型 ID 启发式,`attachment` 仍为 `null`),尽管模型.dev 将该模型列为多模态。经实时模型.dev API 确认:它将 Pixtral 12B 归类在**短** ID `pixtral-12b` 下(`attachment: true`、`modalities.input: ["text","image"]`),而请求使用 Mistral API 别名 `pixtral-12b-latest`。同步查询尝试了精确/原始/静态规范 ID — 全部错过短形式 — 因此落入启发式。`getSyncedCapabilityForResolved` 现在添加了最后手段容灾,去掉尾部 `-latest` 重试,使同步元数据(`attachment` / 图像模态)对这些别名生效;其 `-latest` ID 以完整形式存储的模型(如 `pixtral-large-latest`)继续直接解析。注意:模型.dev 同步目前仅支持手动(Settings → 模型.dev),没有定时刷新,因此新实例在同步运行前仍依赖 #4071 启发式 — 定期刷新节奏留作后续跟进。([#4073](https://github.com/diegosouzapw/OmniRoute/issues/4073) — 感谢 @diego-anselmo) +- **fix(sse): 将 Xiaomi MiMo 推理控制映射到其原生 `thinking:{type}` 形式** — MiMo(`api.xiaomimimo.com`)**仅**通过顶级 `thinking:{type:"enabled"|"disabled"}` 控制思维链,不理解 OpenAI 的 `reasoning_effort`/`reasoning`,而其请求校验器是严格的(`400 Param Incorrect`)。OmniRoute 的 OpenAI 路径将推理意图作为 `reasoning_effort` 携带,而 claude→openai 翻译器可能留下 Claude 形式的 `thinking:{type, budget_tokens}` — 因此客户端的开关选择被静默丢弃,`budget_tokens`/`reasoning_effort` 作为额外参数附带,校验器可能拒绝。新的 `open-sse/services/mimoThinking.ts::normalizeMimoThinking`(在 `chatCore` 中为 `provider==="xiaomi-mimo"` 接入)将任何 thinking 对象精简为仅 `{type}`(`disabled` 保留;`enabled`/`adaptive`/其他 → `enabled`)并丢弃 `reasoning_effort`/`reasoning`。它故意**不**从裸 `reasoning_effort` 合成 thinking — `mimo-v2-omni` 是非思考模型,因此这可能会将静默忽略的参数变成硬错误。([#4224](https://github.com/diegosouzapw/OmniRoute/pull/4224)) +- **fix(capabilities): Xiaomi MiMo `*-pro` 聊天模型仅支持文本(无视觉)** — 根据 Xiaomi 文档,仅 `mimo-v2.5` 和 `mimo-v2-omni` 接受图像;`mimo-v2.5-pro`/`mimo-v2-pro` 仅支持文本,但 `modelSpecs` 将其标记为支持视觉,且模型.dev 也错误标记([hermes-agent#18884](https://github.com/NousResearch/hermes-agent/issues/18884))。由于 `resolveVisionCapability` 让同步的 `attachment:true` 优先,图像请求可能被路由到不支持视觉的模型(#4071 故障模式)。修正了规格**并**在 `resolveVisionCapability` 中添加了硬覆盖(在同步分支之前检查,锚定使 `mimo-v2.5-pro` 永不匹配多模态 `mimo-v2.5`),覆盖错误的同步 attachment。还注册了缺失的原生 `mimo-v2-pro` 聊天模型和缺失的 `mimo-v2-tts` 语音模型。([#4224](https://github.com/diegosouzapw/OmniRoute/pull/4224)) +- **fix(sse): Claude Opus 4.7+/Fable 5 仅使用自适应思考(不再有手动预算 400)** — Opus 4.7 及更高版本(Opus 4.7/4.8、Fable 5)移除了手动扩展思考:`thinking.type:"enabled"` 或**任何** `thinking.budget_tokens` 现在返回 `400`("Any request that tries to set a fixed thinking budget gets a 400" — Anthropic 迁移指南)。推理仅支持自适应,由 `output_config.effort` 控制。OmniRoute 的 OpenAI→Claude 翻译器将 `reasoning_effort` 低/中/高映射到手动 `thinking:{type:"enabled", budget_tokens}`,因此这些请求在最常用的服务商上硬 400(发送旧格式的 Claude 原生透传客户端也是如此)。新的 `adaptiveThinkingOnly` 模型标志现在驱动两项修复:翻译器将**每个**级别的 `reasoning_effort` 映射到 `{type:"adaptive"}` + `output_config.effort`(保留请求级别,永不设预算)用于这些模型,并且在现有翻译后思考规范化阻塞点的 `normalizeClaudeAdaptiveThinking` 兜底将任何残留的手动思考(透传旧格式、每模型默认值)折叠为 `{type:"adaptive"}`,以解析后的上游模型为键,覆盖所有路由模式。4.7 之前的模型(Opus 4.6/4.5、Sonnet、Haiku)保持手动预算不变。([#4230](https://github.com/diegosouzapw/OmniRoute/pull/4230)) +- **fix(providers): 为 Claude Opus 4.7+/Fable 5 剥离非默认 temperature/top_p/top_k(固定采样 → 无 400)** — Opus 4.7 及更高版本以 `400` 拒绝非默认 `temperature`/`top_p`/`top_k`(采样固定;推理移至 `output_config.effort`)。翻译器无条件转发客户端提供的 `temperature`/`top_p`,且 Claude 注册表模型没有 `unsupportedParams`,因此对 `claude-opus-4-8` 的普通 OpenAI 格式请求 `temperature: 0.7` 硬 400。将 `unsupportedParams: ["temperature","top_p","top_k"]` 添加到 `claude`(短横线 `claude-opus-4-8`)和 `anthropic`(点号 `claude-opus-4.7`)注册表中的 Opus 4.7+/Fable 5 ID,使其在现有 `getUnsupportedParams` 调度阻塞点被剥离。4.7 之前的 Claude 模型仍接受采样参数。([#4230](https://github.com/diegosouzapw/OmniRoute/pull/4230)) +- **fix(providers): 在 `openai` Chat Completions 路径上有条件剥离 GPT-5 推理的 temperature/top_p(推理激活时无 400)** — GPT-5 推理模型在推理激活时以 `400` 拒绝非默认 `temperature`/`top_p`,但在 `reasoning_effort:"none"`(GPT-5.1+ 默认值,即非推理模式)下又接受它们。在 `openai` 服务商上,仅 `o3` 带有 `REASONING_UNSUPPORTED`;`gpt-5.5`/`gpt-5.4`/`gpt-5.4-mini`/`gpt-5.4-nano` 没有采样守卫,因此 `temperature` + 激活推理请求硬 400。静态 `unsupportedParams` 列表无法表达 `none` 模式例外(会过度剥离合法情况),因此新的 `gpt5SamplingGuard` 仅在解析后的推理激活时丢弃 `temperature`/`top_p` — 接入现有 `getUnsupportedParams` 阻塞点,范围限定为 `openai` Chat Completions 接口(`codex` Responses 路径已由 CodexExecutor 允许列表覆盖;其他服务商不受影响)。([#4245](https://github.com/diegosouzapw/OmniRoute/pull/4245)) +- **fix(codex): 停止静默丢弃 GPT-5 输出详细程度(`verbosity` / `text.verbosity`)** — GPT-5 系列新增输出详细程度控制:Chat Completions 上的 `verbosity`(low/medium/high),嵌套为 Responses API 上的 `text.verbosity`。CodexExecutor 通过允许列表过滤翻译后的请求,该列表没有 `text` 条目,因此对于 `codex` 服务商,提示在到达上游前被丢弃(`openai` Chat 路径已经转发它)。`normalizeCodexVerbosity` 现在在允许列表(现在允许 `text`)之前将到达的任何形式折叠为单个经过校验的 `text:{verbosity}`,并且 OpenAI Chat↔Responses 请求翻译器跨格式映射 `verbosity`,使提示在非 codex Responses 后端的格式转换中也能保留。无效/缺失的详细程度折叠为无 `text`(现状)。([#4245](https://github.com/diegosouzapw/OmniRoute/pull/4245)) +- **fix(sse): 将 `reasoning_effort` 映射到 DeepSeek V4 的原生 `{high, max}` 词汇** — DeepSeek V4 仅理解 `high`/`max` 推理级别,因此其他 `reasoning_effort` 值被映射到其原生词汇而非被拒绝。([#4219](https://github.com/diegosouzapw/OmniRoute/pull/4219)) +- **fix(glm): 为 GLM-5.2+ 思考设置默认 `max_tokens` 和延长超时** — GLM-5.2+ 思考响应较慢且需要余量,因此 OmniRoute 现在为其设置合理的默认 `max_tokens` 和更长的超时。([#4255](https://github.com/diegosouzapw/OmniRoute/pull/4255) — 感谢 @dhaern) +- **fix(antigravity): 现代 Gemini 模型的默认 `includeThoughts`** — Antigravity 路径上的现代 Gemini 模型现在默认包含思考,使推理不会被静默丢弃。([#4180](https://github.com/diegosouzapw/OmniRoute/pull/4180) — 感谢 @dhaern) +- **fix(provider-registry): 为 theoldllm 模型添加正确的 `contextLength`** — 为 theoldllm 的模型填入准确的上下文窗口大小。([#4184](https://github.com/diegosouzapw/OmniRoute/pull/4184) — 感谢 @herjarsa) +- **fix(models): 暴露组合模型令牌限制** — `/v1/models` 现在报告组合模型的令牌限制。([#4189](https://github.com/diegosouzapw/OmniRoute/pull/4189) — 感谢 @megamen32) +- **fix(combo): 保持透传配额容灾的范围限制** — 防止透传配额容灾泄漏到无关目标。([#4194](https://github.com/diegosouzapw/OmniRoute/pull/4194) — 感谢 @Svetznaniy33) +- **fix(combo): 将主动容灾压缩纳入 TV1 逃生机制(无静默目标丢弃)** — 主动容灾压缩现在参与 TV1 逃生机制,确保目标永不静默丢弃。([#4228](https://github.com/diegosouzapw/OmniRoute/pull/4228)) +- **fix(compression): 显示引擎预览输出** — 压缩 Studio 预览现在渲染引擎的输出。([#4128](https://github.com/diegosouzapw/OmniRoute/pull/4128) — 感谢 @megamen32) +- **fix(compression): 加固引擎应对 I/O 故障和错误配置(F5.3)** — 压缩引擎在 I/O 错误和错误配置时优雅降级而非抛出异常。([#4198](https://github.com/diegosouzapw/OmniRoute/pull/4198)) +- **fix(compression): 加固 RTK 原始输出脱敏 + 自定义过滤器的 ReDoS 守卫(F5.3)** — 扩大 RTK 原始输出脱敏范围并为用户提供的过滤器模式添加 ReDoS 守卫。([#4203](https://github.com/diegosouzapw/OmniRoute/pull/4203)) +- **fix(compression): 在实时读取路径上限制 `mcpAccessibility` `maxTextChars`** — 实时读取路径现在钳制 `maxTextChars`,防止过小的值导致工具消失。([#4206](https://github.com/diegosouzapw/OmniRoute/pull/4206)) +- **fix(dashboard): 数据表格绘制不透明表面,防止网格透出** — 数据表格现在在不透明表面上渲染,修复网格壁纸透出的问题。([#4233](https://github.com/diegosouzapw/OmniRoute/pull/4233)) +- **fix(dashboard): 使服务商卡片悬停可见(之前约 1% 不透明度)** — 服务商卡片悬停状态之前几乎不可见;现在具有可见的表面。([#4214](https://github.com/diegosouzapw/OmniRoute/pull/4214)) +- **fix(vscode): 清理隐式编辑器上下文** — 在发送到上游之前,从隐式 VS Code 编辑器上下文中脱敏敏感文件名/关键词。([#4124](https://github.com/diegosouzapw/OmniRoute/pull/4124) — 感谢 @zhiru) +- **fix(build): 提高本地 `next build` 的 Node 堆内存以停止 OOM/卡顿** — 提高构建时堆内存,使本地生产构建不再 OOM 或卡顿。([#4171](https://github.com/diegosouzapw/OmniRoute/pull/4171)) +- **fix(mitm): 基于 OUTPUT 链的 TPROXY 方案用于本地流量(VPS 端到端验证)** — 将 TPROXY 规则切换为 OUTPUT 链方案,使本地发起的流量被捕获;在 VPS 上端到端验证。([#4156](https://github.com/diegosouzapw/OmniRoute/pull/4156)) +- **fix(mitm): 转发反循环 — 将绕过标记的套接字放在 Agent 上(解密 4d)** — 将绕过标记的套接字放在 HTTP Agent 上,使 OmniRoute 自身的转发流量永不重新进入捕获循环;VPS 验证。([#4229](https://github.com/diegosouzapw/OmniRoute/pull/4229)) +- **fix(free-tiers): 退役失效的 `hasFree` 层级,标题四舍五入为 ~1.6B,重新生成每服务商表格** — 从标题计算中移除失效的免费层级并重新生成每服务商免费层级表格。([#4142](https://github.com/diegosouzapw/OmniRoute/pull/4142)) +- **fix(free-tiers): 退役 4 个重新验证已失效的免费层级,标记 iflytek/sparkdesk ToS,澄清 monsterapi 一次性** — 移除四个确认已失效的免费层级并标注 ToS/一次性注意事项。([#4152](https://github.com/diegosouzapw/OmniRoute/pull/4152)) -### 🧪 Tests +### 🧪 测试 -- **test(sse): guard the Antigravity `_toolNameMap` cloak map through the request-capture round-trip** — follow-up to #4091: the generic capture fix in `createPreparedRequestLogger().body()` (#4153) re-attaches the non-enumerable `_toolNameMap` that the request-inspector drops when it rebuilds the upstream body via `JSON.parse(JSON.stringify(...))`, but the only regression test covered the native-Claude OAuth cloak (PascalCase aliases). The Antigravity cloak differs — `cloakAntigravityToolPayload` suffixes custom tools with `_ide` (`workspace_read` → `workspace_read_ide`), leaves native tools untouched, and returns the reverse map separately — so a refactor of `providerRequestLogging.ts` or the executor could silently re-break Antigravity tool dispatch without tripping the Claude test. Adds a dedicated regression test driving the real `cloakAntigravityToolPayload` through the capture round-trip and asserting the `_ide` reverse map survives, stays non-enumerable (never re-serializes upstream), and that all-native traffic produces no spurious map (verified failing with the #4153 re-attach removed). No production change. ([#4181](https://github.com/diegosouzapw/OmniRoute/issues/4181) — thanks @hertznsk) -- **test(chatcore): dedicated unit tests for 6 leaves + wire into stryker mutate (QG v2 Fase 9 T5 Fase 3)** — adds focused unit tests for 6 chatCore leaf helpers and enrolls them in mutation testing. ([#4218](https://github.com/diegosouzapw/OmniRoute/pull/4218)) -- **test(chatcore): telemetry / memory-skills / semantic-cache tests + wire 2 into stryker (QG v2 Fase 9 T5 Fase 3)** — new tests for the telemetry, memory-skills and semantic-cache leaves, two of which are added to the mutation set. ([#4222](https://github.com/diegosouzapw/OmniRoute/pull/4222)) -- **test+ci(chatcore): semanticCache HIT-path fixture (15/15 mutate) + 350min budget headroom** — closes the semantic-cache HIT path to a full 15/15 mutation score and gives the nightly auth/accountFallback batches more budget headroom. ([#4225](https://github.com/diegosouzapw/OmniRoute/pull/4225)) -- **test(compression): close F5.1 coverage gaps (replay reducer, live accumulator, StatusDot)** — fills the remaining F5.1 compression coverage gaps. ([#4192](https://github.com/diegosouzapw/OmniRoute/pull/4192)) -- **test(db,sse): de-flake db-backup + chatcore streaming timing assertions** — stabilizes two timing-sensitive tests (fire-and-forget backup completion + a streaming race). ([#4132](https://github.com/diegosouzapw/OmniRoute/pull/4132)) -- **test: align stale integration tests surfaced post-v3.8.28 on main** — realigns integration tests that drifted after the v3.8.28 merge. ([#4129](https://github.com/diegosouzapw/OmniRoute/pull/4129)) +- **test(sse): 通过请求捕获往返守卫 Antigravity `_toolNameMap` 伪装映射** — #4091 的后续:`createPreparedRequestLogger().body()`(#4153)中的通用捕获修复重新附加了请求检查器在通过 `JSON.parse(JSON.stringify(...))` 重建上游体时丢弃的不可枚举 `_toolNameMap`,但唯一的回归测试仅覆盖了原生 Claude OAuth 伪装(PascalCase 别名)。Antigravity 伪装不同 — `cloakAntigravityToolPayload` 为自定义工具添加 `_ide` 后缀(`workspace_read` → `workspace_read_ide`),保持原生工具不变,并单独返回反向映射 — 因此 `providerRequestLogging.ts` 或执行器的重构可能在不触发 Claude 测试的情况下静默重新破坏 Antigravity 工具调度。新增专用回归测试,驱动真实的 `cloakAntigravityToolPayload` 通过捕获往返,断言 `_ide` 反向映射存活、保持不可枚举(永不重新序列化到上游),并且全原生流量不产生伪映射(验证在移除 #4153 重新附加时失败)。无生产变更。([#4181](https://github.com/diegosouzapw/OmniRoute/issues/4181) — 感谢 @hertznsk) +- **test(chatcore): 6 个叶函数的专用单元测试 + 接入 stryker mutate(QG v2 Fase 9 T5 Fase 3)** — 为 6 个 chatCore 叶辅助函数添加聚焦单元测试并将其纳入变异测试。([#4218](https://github.com/diegosouzapw/OmniRoute/pull/4218)) +- **test(chatcore): 遥测 / 记忆技能 / 语义缓存测试 + 将 2 个接入 stryker(QG v2 Fase 9 T5 Fase 3)** — 遥测、记忆技能和语义缓存叶函数的新测试,其中两个被添加到变异集。([#4222](https://github.com/diegosouzapw/OmniRoute/pull/4222)) +- **test+ci(chatcore): semanticCache HIT 路径夹具(15/15 mutate)+ 350 分钟预算余量** — 将语义缓存 HIT 路径推进到完整的 15/15 变异分数,并为夜间认证/accountFallback 批次提供更多预算余量。([#4225](https://github.com/diegosouzapw/OmniRoute/pull/4225)) +- **test(compression): 填补 F5.1 覆盖率缺口(回放 reducer、实时累加器、StatusDot)** — 填补剩余的 F5.1 压缩覆盖率缺口。([#4192](https://github.com/diegosouzapw/OmniRoute/pull/4192)) +- **test(db,sse): 去抖动 db-backup + chatcore 流式传输时序断言** — 稳定两个时序敏感测试(即发即忘备份补全 + 流式传输竞态)。([#4132](https://github.com/diegosouzapw/OmniRoute/pull/4132)) +- **test: 对齐 v3.8.28 之后在 main 上浮现的过时集成测试** — 重新对齐 v3.8.28 合并后漂移的集成测试。([#4129](https://github.com/diegosouzapw/OmniRoute/pull/4129)) -### 📝 Maintenance +### 📝 维护 -- **refactor(sse): split chatCore.ts pure helpers into chatCore/ modules (−561 LOC)** — extracts pure helpers out of the chatCore god-file into dedicated modules (Onda 3). ([#4159](https://github.com/diegosouzapw/OmniRoute/pull/4159)) -- **refactor(chatcore): extract passthrough/header/telemetry helpers (QG v2 Fase 9 T5 C2-C3-C5)** — further chatCore decomposition. ([#4188](https://github.com/diegosouzapw/OmniRoute/pull/4188)) -- **refactor(chatcore): extract combo/proxy context cache + semaphore helpers (QG v2 Fase 9 T5 C6-C7)** — continues the chatCore split. ([#4193](https://github.com/diegosouzapw/OmniRoute/pull/4193)) -- **refactor(combo): god-file split pilot — types + validateQuality + predicates (QG v2 Fase 9 T5 D1-D3)** — first slice of the combo.ts decomposition. ([#4162](https://github.com/diegosouzapw/OmniRoute/pull/4162)) -- **refactor(combo): god-file split part 2 — shadow + sorters + structure (QG v2 Fase 9 T5 D4-D6)** — continues the combo.ts split. ([#4175](https://github.com/diegosouzapw/OmniRoute/pull/4175)) -- **refactor(combo): god-file split part 3 — auto strategy (QG v2 Fase 9 T5 D8)** — extracts the auto strategy from combo.ts. ([#4186](https://github.com/diegosouzapw/OmniRoute/pull/4186)) -- **refactor(combo): extract round-robin sticky state to `combo/rrState.ts` (D7a)** — moves round-robin sticky state into its own module. ([#4196](https://github.com/diegosouzapw/OmniRoute/pull/4196)) -- **refactor(combo): extract the reset-aware quota block to `combo/quotaStrategies.ts` (D7b)** — moves the reset-aware quota strategies into their own module. ([#4204](https://github.com/diegosouzapw/OmniRoute/pull/4204)) -- **refactor(compression): remove vestigial SLM seam + dead deprecated alias** — drops dead compression code. ([#4253](https://github.com/diegosouzapw/OmniRoute/pull/4253)) -- **chore(compression): remove vestigial reconstructCcr/SessionDedup round-trip helpers** — removes unused round-trip helpers. ([#4226](https://github.com/diegosouzapw/OmniRoute/pull/4226)) -- **chore(compression): remove dead exports + fix stale llmlingua docs** — prunes dead exports and corrects stale LLMLingua docs. ([#4223](https://github.com/diegosouzapw/OmniRoute/pull/4223)) -- **chore(build): build + ship the TPROXY native addon in the standalone (prebuilds 4e)** — bundles the native TPROXY addon prebuilds into the standalone build. ([#4236](https://github.com/diegosouzapw/OmniRoute/pull/4236)) -- **chore(ci): add quota + 6 covered chatCore leaves to stryker mutate (QG v2 Fase 9 T5 Fase 3 follow-up)** — enrolls more covered leaves into mutation testing. ([#4209](https://github.com/diegosouzapw/OmniRoute/pull/4209)) -- **chore(ci): re-add 8 combo split leaves to stryker mutate + expand nightly batch-matrix 3→5 (QG v2 Fase 9 T5 Fase 3)** — restores mutation coverage for the split combo leaves and widens the nightly matrix. ([#4205](https://github.com/diegosouzapw/OmniRoute/pull/4205)) -- **chore(quality): close v3.8.28 cycle gate drift (re-baseline + nightly-mutation scope)** — reconciles quality-gate baselines after the v3.8.28 cycle. ([#4135](https://github.com/diegosouzapw/OmniRoute/pull/4135)) -- **ci(mutation): split nightly into 3 parallel batches to fit the 180min budget (QG v2 Fase 9 T0)** — parallelizes the nightly mutation run. ([#4150](https://github.com/diegosouzapw/OmniRoute/pull/4150)) -- **ci(mutation): restore cold-seed timeout headroom (a/b lost in #4225 squash) + extend to c/d/g/h** — restores and extends per-batch cold-seed timeouts. ([#4258](https://github.com/diegosouzapw/OmniRoute/pull/4258)) -- **ci(docs): harden the fabricated-docs checker + enforce `--strict` (QG v2 Fase 9 T9)** — tightens the anti-hallucination docs checker. ([#4149](https://github.com/diegosouzapw/OmniRoute/pull/4149)) -- **ci: derive the oasdiff base-ref from the package version + flag the mutation-toolchain regression** — fixes the OpenAPI-diff base-ref and surfaces a mutation-toolchain regression. ([#4134](https://github.com/diegosouzapw/OmniRoute/pull/4134)) -- **docs(ci): correct the mutation-gate note (no regression — `stryker -c` is `--concurrency`); record Task 12 GO** — corrects a misread of the stryker flag and records the spike GO. ([#4138](https://github.com/diegosouzapw/OmniRoute/pull/4138)) -- **docs(api): document the `/api/v1/ws` chat WebSocket endpoint in openapi.yaml** — adds the WebSocket chat endpoint to the OpenAPI spec. ([#4215](https://github.com/diegosouzapw/OmniRoute/pull/4215)) -- **docs(readme): expand Acknowledgments into a themed, star-counted credits hall** — reworks the README acknowledgments section. ([#4195](https://github.com/diegosouzapw/OmniRoute/pull/4195)) -- **style(dashboard): shrink the identity grid cell 46px → 32px (~30% smaller)** — tightens the identity grid density. ([#4143](https://github.com/diegosouzapw/OmniRoute/pull/4143)) +- **refactor(sse): 将 chatCore.ts 纯辅助函数拆分到 chatCore/ 模块(−561 LOC)** — 将纯辅助函数从 chatCore 上帝文件中提取到专用模块(Onda 3)。([#4159](https://github.com/diegosouzapw/OmniRoute/pull/4159)) +- **refactor(chatcore): 提取透传/请求头/遥测辅助函数(QG v2 Fase 9 T5 C2-C3-C5)** — 进一步 chatCore 分解。([#4188](https://github.com/diegosouzapw/OmniRoute/pull/4188)) +- **refactor(chatcore): 提取组合/代理上下文缓存 + 信号量辅助函数(QG v2 Fase 9 T5 C6-C7)** — 继续 chatCore 拆分。([#4193](https://github.com/diegosouzapw/OmniRoute/pull/4193)) +- **refactor(combo): 上帝文件拆分试点 — 类型 + validateQuality + 谓词(QG v2 Fase 9 T5 D1-D3)** — Combo.ts 分解的第一部分。([#4162](https://github.com/diegosouzapw/OmniRoute/pull/4162)) +- **refactor(combo): 上帝文件拆分第 2 部分 — shadow + 排序器 + 结构(QG v2 Fase 9 T5 D4-D6)** — 继续 Combo.ts 拆分。([#4175](https://github.com/diegosouzapw/OmniRoute/pull/4175)) +- **refactor(combo): 上帝文件拆分第 3 部分 — auto 策略(QG v2 Fase 9 T5 D8)** — 从 Combo.ts 中提取 auto 策略。([#4186](https://github.com/diegosouzapw/OmniRoute/pull/4186)) +- **refactor(combo): 将轮询粘性状态提取到 `combo/rrState.ts`(D7a)** — 将轮询粘性状态移到自己的模块中。([#4196](https://github.com/diegosouzapw/OmniRoute/pull/4196)) +- **refactor(combo): 将重置感知配额块提取到 `combo/quotaStrategies.ts`(D7b)** — 将重置感知配额策略移到自己的模块中。([#4204](https://github.com/diegosouzapw/OmniRoute/pull/4204)) +- **refactor(compression): 移除残留 SLM 接缝 + 已弃用的死别名** — 删除死压缩代码。([#4253](https://github.com/diegosouzapw/OmniRoute/pull/4253)) +- **chore(compression): 移除残留的 reconstructCcr/SessionDedup 往返辅助函数** — 移除未使用的往返辅助函数。([#4226](https://github.com/diegosouzapw/OmniRoute/pull/4226)) +- **chore(compression): 移除死导出 + 修复过时的 llmlingua 文档** — 修剪死导出并修正过时的 LLMLingua 文档。([#4223](https://github.com/diegosouzapw/OmniRoute/pull/4223)) +- **chore(build): 在独立构建中构建并打包 TPROXY 原生插件(预构建 4e)** — 将原生 TPROXY 插件预构建包打包到独立构建中。([#4236](https://github.com/diegosouzapw/OmniRoute/pull/4236)) +- **chore(ci): 将配额 + 6 个已覆盖的 chatCore 叶函数添加到 stryker mutate(QG v2 Fase 9 T5 Fase 3 后续)** — 将更多已覆盖叶函数纳入变异测试。([#4209](https://github.com/diegosouzapw/OmniRoute/pull/4209)) +- **chore(ci): 将 8 个组合拆分叶函数重新添加到 stryker mutate + 扩展夜间批次矩阵 3→5(QG v2 Fase 9 T5 Fase 3)** — 恢复拆分 Combo 叶函数的变异覆盖率并扩展夜间矩阵。([#4205](https://github.com/diegosouzapw/OmniRoute/pull/4205)) +- **chore(quality): 关闭 v3.8.28 周期门漂移(重新基线化 + 夜间变异范围)** — 调和 v3.8.28 周期后的质量门基线。([#4135](https://github.com/diegosouzapw/OmniRoute/pull/4135)) +- **ci(mutation): 将夜间任务拆分为 3 个并行批次以适应 180 分钟预算(QG v2 Fase 9 T0)** — 并行化夜间变异运行。([#4150](https://github.com/diegosouzapw/OmniRoute/pull/4150)) +- **ci(mutation): 恢复冷种子超时余量(a/b 在 #4225 合并中丢失)+ 扩展到 c/d/g/h** — 恢复并扩展每批次冷种子超时。([#4258](https://github.com/diegosouzapw/OmniRoute/pull/4258)) +- **ci(docs): 加固虚构文档检查器 + 强制执行 `--strict`(QG v2 Fase 9 T9)** — 收紧反幻觉文档检查器。([#4149](https://github.com/diegosouzapw/OmniRoute/pull/4149)) +- **ci: 从包版本派生 oasdiff base-ref + 标记变异工具链回归** — 修复 OpenAPI-diff base-ref 并暴露变异工具链回归。([#4134](https://github.com/diegosouzapw/OmniRoute/pull/4134)) +- **docs(ci): 更正变异门注释(无回归 — `stryker -c` 是 `--concurrency`);记录 Task 12 GO** — 更正 stryker 标志的误读并记录探索 GO。([#4138](https://github.com/diegosouzapw/OmniRoute/pull/4138)) +- **docs(api): 在 openapi.yaml 中记录 `/api/v1/ws` 聊天 WebSocket 端点** — 将 WebSocket 聊天端点添加到 OpenAPI 规范中。([#4215](https://github.com/diegosouzapw/OmniRoute/pull/4215)) +- **docs(readme): 将 Acknowledgments 扩展为主题化、带星标的致谢大厅** — 重做 README 致谢部分。([#4195](https://github.com/diegosouzapw/OmniRoute/pull/4195)) +- **style(dashboard): 将标识网格单元格缩小 46px → 32px(约小 30%)** — 收紧标识网格密度。([#4143](https://github.com/diegosouzapw/OmniRoute/pull/4143)) -### 🔧 Dependencies +### 🔧 依赖 -- **deps: bump the production group with 5 updates** — routine production-dependency bumps. ([#4121](https://github.com/diegosouzapw/OmniRoute/pull/4121)) -- **chore(deps): bump github/codeql-action from 3 to 4** — CI action update. ([#4120](https://github.com/diegosouzapw/OmniRoute/pull/4120)) -- **chore(deps): bump actions/setup-python from 5 to 6** — CI action update. ([#4119](https://github.com/diegosouzapw/OmniRoute/pull/4119)) +- **deps: 将生产组更新 5 项** — 例行生产依赖更新。([#4121](https://github.com/diegosouzapw/OmniRoute/pull/4121)) +- **chore(deps): 将 github/codeql-action 从 3 更新到 4** — CI action 更新。([#4120](https://github.com/diegosouzapw/OmniRoute/pull/4120)) +- **chore(deps): 将 actions/setup-python 从 5 更新到 6** — CI action 更新。([#4119](https://github.com/diegosouzapw/OmniRoute/pull/4119)) --- ## [3.8.28] — 2026-06-17 -### ✨ New Features +### ✨ 新功能 -- **feat(providers): add OrcaRouter (OpenAI-compatible routing gateway)** — OrcaRouter is now registered as an API-key provider. Its adaptive router is exposed as `orcarouter/auto` (smart routing across 150+ upstream models), alongside a curated flagship set (GPT-5.5, Gemini 3.5 Flash, Claude Opus 4.8, Grok 4.3, DeepSeek V4 Pro, MiniMax M2.7, Qwen3.7 Max). `passthroughModels` is enabled so any OrcaRouter model id works. OpenAI-compatible endpoint (`https://api.orcarouter.ai/v1`), Bearer (`sk-orca-…`) auth — no custom executor or translator required. ([#4070](https://github.com/diegosouzapw/OmniRoute/pull/4070) — thanks @jinhaosong-source) -- **feat(providers): add Wafer AI (Anthropic-compatible, Bearer auth)** — Wafer AI is now a built-in provider speaking the Anthropic Messages format with Bearer authentication, registered with its model catalog so it works out of the box. ([#4098](https://github.com/diegosouzapw/OmniRoute/pull/4098) — thanks @diegosouzapw) -- **feat(cli): `omniroute launch` — zero-config Claude Code launcher** — a new CLI subcommand that boots OmniRoute (if not already running) and launches Claude Code pre-wired to it, with no manual env/settings editing. ([#4097](https://github.com/diegosouzapw/OmniRoute/pull/4097) — thanks @diegosouzapw) -- **feat(api): exact offline token counting for the `count_tokens` fallback via tiktoken** — the local `count_tokens` fallback now uses a real tiktoken (BPE) tokenizer for exact offline counts instead of a heuristic estimate, so token budgeting is accurate even when no upstream count endpoint is reachable. ([#4087](https://github.com/diegosouzapw/OmniRoute/pull/4087) — thanks @diegosouzapw) -- **feat(sse): Claude Code quota-probe bypass + command meta-request helpers** — ported from free-claude-code: OmniRoute now recognizes Claude Code's quota-probe and command meta-requests and serves them locally instead of burning an upstream call, reducing wasted quota during CLI sessions. ([#4083](https://github.com/diegosouzapw/OmniRoute/pull/4083) — thanks @diegosouzapw) -- **feat(sse): generic 400 field-downgrade retry + Groq field stripping** — when an upstream rejects a request with `400` because of an unsupported field, OmniRoute now strips the offending field and retries (a generic downgrade path), with Groq-specific field stripping wired in. Aligned with the existing `context_management` retry handling. ([#4096](https://github.com/diegosouzapw/OmniRoute/pull/4096) — thanks @diegosouzapw) -- **feat(sse): delegated Anthropic Context Editing — relay coverage + 400-fallback** — extends the Claude server-side Context Editing delegation (#4021) with broader relay coverage and a `400`-fallback so a request the upstream rejects for the context-management beta degrades gracefully instead of failing. ([#4065](https://github.com/diegosouzapw/OmniRoute/pull/4065) — thanks @diegosouzapw) -- **feat(compression): record per-engine Context Editing telemetry** — the compression pipeline now records a `context-editing` engine entry so the dashboard attributes server-side Context Editing savings alongside the local compression engines. ([#4062](https://github.com/diegosouzapw/OmniRoute/pull/4062) — thanks @diegosouzapw) -- **feat(compression): RTK learn/discover (sample source + API + UI)** — the rule-based RTK compression engine gains a learn/discover workflow: sample a source, surface candidate rules through a new API, and review/apply them in the dashboard. ([#4088](https://github.com/diegosouzapw/OmniRoute/pull/4088) — thanks @diegosouzapw) -- **feat(dashboard): 2026-06-17 free-tier refresh — honest catalog, uncapped + boost tiers, Layout A budget table** — the free-tier page was refreshed with an honest, deep-researched catalog (pooled/realistic figures rather than inflated 24/7 RPM math), new `recurring-uncapped` and boost tiers, new providers, and a KPI + budget table (Layout A). ([#4089](https://github.com/diegosouzapw/OmniRoute/pull/4089) — thanks @diegosouzapw) -- **feat(dashboard): Combo Studio connection-cooldown badge (U1b Slice 2)** — the Combo Live cascade now surfaces each connection's cooldown state as a badge, complementing the circuit-breaker badge shipped in 3.8.27. ([#4068](https://github.com/diegosouzapw/OmniRoute/pull/4068) — thanks @diegosouzapw) -- **feat(mitm): attribute intercepted requests to the originating process (Gap 1)** — the Traffic Inspector now resolves each intercepted connection back to the originating local process (via `/proc`), so captured traffic can be attributed to the app that produced it. (ProxyBridge-inspired hardening.) ([#4085](https://github.com/diegosouzapw/OmniRoute/pull/4085) — thanks @diegosouzapw) -- **feat(mitm): capture-pipeline self-test route (Gap 12)** — a diagnostic route that exercises the MITM capture pipeline end-to-end so operators can confirm interception is working without crafting a real upstream call. ([#4093](https://github.com/diegosouzapw/OmniRoute/pull/4093) — thanks @diegosouzapw) -- **feat(mitm): loop-guard self-check + verbosity control in `server.cjs` (Gaps 14+15)** — the MITM proxy gains a self-referential loop guard (so it never proxies its own traffic into an infinite loop) and a `MITM_VERBOSE` routing-decision log level. ([#4101](https://github.com/diegosouzapw/OmniRoute/pull/4101) — thanks @diegosouzapw) -- **feat(agent-bridge): portable JSON import/export of config (Gap 4)** — the Agent Bridge / MITM configuration can now be exported to and imported from a portable JSON file, so a working setup can be backed up or moved between machines. ([#4094](https://github.com/diegosouzapw/OmniRoute/pull/4094) — thanks @diegosouzapw) +- **feat(providers): add OrcaRouter (OpenAI-compatible routing gateway)** — OrcaRouter 现已注册为 API-key 服务商。其自适应路由器以 `orcarouter/auto` 暴露(跨 150+ 上游模型的智能路由),以及精选旗舰模型系列(GPT-5.5、Gemini 3.5 Flash、Claude Opus 4.8、Grok 4.3、DeepSeek V4 Pro、MiniMax M2.7、Qwen3.7 Max)。已启用 `passthroughModels`,因此任何 OrcaRouter 模型 ID 均可使用。OpenAI 兼容端点(`https://api.orcarouter.ai/v1`),Bearer(`sk-orca-…`)认证 — 无需自定义 executor 或 translator。([#4070](https://github.com/diegosouzapw/OmniRoute/pull/4070) — 感谢 @jinhaosong-source) +- **feat(providers): add Wafer AI (Anthropic-compatible, Bearer auth)** — Wafer AI 现已成为内置服务商,使用 Anthropic Messages 格式和 Bearer 认证,已注册其模型目录,开箱即用。([#4098](https://github.com/diegosouzapw/OmniRoute/pull/4098) — 感谢 @diegosouzapw) +- **feat(cli): `omniroute launch` — 零配置 Claude Code launcher** — 新的 CLI 子命令,启动 OmniRoute(如果尚未运行)并启动预先配置好的 Claude Code,无需手动编辑 env/settings。([#4097](https://github.com/diegosouzapw/OmniRoute/pull/4097) — 感谢 @diegosouzapw) +- **feat(api): exact offline token counting for the `count_tokens` fallback via tiktoken** — 本地 `count_tokens` 容灾现在使用真正的 tiktoken(BPE)分词器进行精确离线计数,而非启发式估算,因此即使上游计数端点不可达,token 预算也能保持准确。([#4087](https://github.com/diegosouzapw/OmniRoute/pull/4087) — 感谢 @diegosouzapw) +- **feat(sse): Claude Code quota-probe bypass + command meta-request helpers** — 移植自 free-claude-code:OmniRoute 现在能识别 Claude Code 的配额探测和命令元请求,并在本地直接响应,而非消耗上游调用,从而减少 CLI 会话中的配额浪费。([#4083](https://github.com/diegosouzapw/OmniRoute/pull/4083) — 感谢 @diegosouzapw) +- **feat(sse): generic 400 field-downgrade retry + Groq field stripping** — 当上游因不支持的字段而返回 `400` 时,OmniRoute 现在会去除该字段并重试(通用降级路径),同时内置了 Groq 专用字段去除逻辑。与现有的 `context_management` 重试处理对齐。([#4096](https://github.com/diegosouzapw/OmniRoute/pull/4096) — 感谢 @diegosouzapw) +- **feat(sse): delegated Anthropic Context Editing — 中继覆盖率 + 400-容灾** — 扩展了 Claude 服务端 Context Editing 委托功能(#4021),提供更广泛的中继覆盖率和 `400`-容灾,使得上游因 context-management beta 而拒绝的请求能优雅降级而非直接失败。([#4065](https://github.com/diegosouzapw/OmniRoute/pull/4065) — 感谢 @diegosouzapw) +- **feat(compression): record per-engine Context Editing telemetry** — 压缩管线现在记录 `context-editing` 引擎条目,使控制台能将服务端 Context Editing 的节省量与本地压缩引擎一并归因显示。([#4062](https://github.com/diegosouzapw/OmniRoute/pull/4062) — 感谢 @diegosouzapw) +- **feat(compression): RTK learn/discover (sample source + API + UI)** — 基于规则的 RTK 压缩引擎新增 learn/discover 工作流:对源进行采样,通过新 API 展示候选规则,并在控制台中审查/应用它们。([#4088](https://github.com/diegosouzapw/OmniRoute/pull/4088) — 感谢 @diegosouzapw) +- **feat(dashboard): 2026-06-17 free-tier refresh — honest catalog, uncapped + boost tiers, Layout A 预算表** — 免费套餐页面已刷新,包含真实、深度调研的目录(共享池/实际数据而非夸大的 24/7 RPM 估算),新增 `recurring-uncapped` 和 boost 套餐层级、新服务商,以及 KPI + 预算表(Layout A)。([#4089](https://github.com/diegosouzapw/OmniRoute/pull/4089) — 感谢 @diegosouzapw) +- **feat(dashboard): Combo Studio connection-cooldown badge (U1b Slice 2)** — Combo Live 级联视图现在展示每个连接的冷却状态徽章,与 3.8.27 中发布的熔断器徽章互为补充。([#4068](https://github.com/diegosouzapw/OmniRoute/pull/4068) — 感谢 @diegosouzapw) +- **feat(mitm): attribute intercepted requests to the originating process (Gap 1)** — 流量检查器现在能将每个被拦截的连接追溯到原始本地进程(通过 `/proc`),使得捕获的流量可以归因到产生它的应用。(ProxyBridge 风格强化。)([#4085](https://github.com/diegosouzapw/OmniRoute/pull/4085) — 感谢 @diegosouzapw) +- **feat(mitm): capture-pipeline self-test route (Gap 12)** — 新增诊断路由,端到端地演练 MITM 捕获管线,使运维人员无需构造真实上游调用即可确认拦截功能正常。([#4093](https://github.com/diegosouzapw/OmniRoute/pull/4093) — 感谢 @diegosouzapw) +- **feat(mitm): loop-guard self-check + verbosity control in `server.cjs` (Gaps 14+15)** — MITM 代理新增自引用循环防护(确保不会将自身流量代理到无限循环中),以及 `MITM_VERBOSE` 路由决策日志级别控制。([#4101](https://github.com/diegosouzapw/OmniRoute/pull/4101) — 感谢 @diegosouzapw) +- **feat(agent-bridge): portable JSON import/export of config (Gap 4)** — Agent Bridge / MITM 配置现可导出并从便携 JSON 文件导入,方便备份工作设置或在机器间迁移。([#4094](https://github.com/diegosouzapw/OmniRoute/pull/4094) — 感谢 @diegosouzapw) -### 🐛 Fixed +### 🐛 问题修复 -- **fix(ws): start the LiveWS sidecar with `cwd` at the package root (global/systemd installs)** — the standalone LiveWS launcher (`scripts/start-ws-server.mjs`) re-spawns itself with `node --import tsx ` but did not set `cwd`. When the WebSocket sidecar was launched from outside the package directory — a global npm/homebrew install, or a `systemd`/`launchd` unit started from `$HOME` — Node could not resolve the `tsx` package (`ERR_MODULE_NOT_FOUND: Cannot find package 'tsx'`), and even from the package directory `tsx` could not resolve the tsconfig `@/*` path aliases (e.g. `@/types/databaseSettings`), so the sidecar never booted. The spawn now pins `cwd` to the package root (the directory above `scripts/`, where `package.json` + `tsconfig.json` live), which resolves both `tsx` discovery and the `@/*` aliases regardless of launch directory. ([#4055](https://github.com/diegosouzapw/OmniRoute/issues/4055) — thanks @Rahulsharma0810) -- **fix(dashboard): Logs page auto-refresh now works in embedded/proxied dashboards** — the Request Logger gated each auto-refresh tick on a static `document.visibilityState === "visible"` read. Hosts that report a permanent non-`"visible"` state without ever firing a `visibilitychange` event (Docker dashboard wrappers, embedded webviews) froze auto-refresh entirely — only the manual Refresh button worked, a regression from 3.8.24's unconditional polling. The pause is now event-driven and fail-open: polling starts enabled and only pauses after a real `visibilitychange` → hidden transition (still preserving the backgrounded-tab optimization for normal browser tabs). ([#4054](https://github.com/diegosouzapw/OmniRoute/issues/4054) — thanks @tjengbudi) -- **fix(docker): raise the build-stage Node heap to stop the production-build OOM** — the Docker `builder` stage ran `npm run build` with V8's default heap ceiling (~2 GB). After #4052 forced the heavier webpack engine (Turbopack panics on this Next.js version), the production optimization pass exceeded that ceiling and the build died with `FATAL ERROR: … JavaScript heap out of memory` at `[builder] npm run build`. The builder stage now sets `NODE_OPTIONS=--max-old-space-size` (default 4096 MB, overridable via `--build-arg OMNIROUTE_BUILD_MEMORY_MB=…`) before the build; the value propagates to the spawned `next build`. Build-only — the runtime heap (`OMNIROUTE_MEMORY_MB` on the runner stage) is unchanged. ([#4076](https://github.com/diegosouzapw/OmniRoute/issues/4076) — thanks @kamenkadmitry) -- **fix(dashboard): "Update Available" banner reappears reliably across Docker/npm/desktop installs** — the home-page banner is gated on `GET /api/system/version`'s `updateAvailable`, which derived the latest version ONLY from `npm info omniroute version --json` via the `npm` CLI binary. When that binary is absent from the runtime PATH (Docker/desktop/locked-down installs) or the registry is unreachable, the call returned `null` → `updateAvailable=false` → the banner silently never rendered even when a newer release existed. The route now resolves the latest version through `resolveLatestVersion()`: the fast `npm` CLI path first, then an npm-binary-free fallback over the registry HTTP API (`registry.npmjs.org/omniroute/latest`), and a logged warning instead of silent degradation when both fail. Version comparison was also hardened to tolerate `v`-prefixed and pre-release version strings. ([#4100](https://github.com/diegosouzapw/OmniRoute/issues/4100)) -- **fix(sse): route image requests only to confirmed-vision combo targets** — a combo could route an image-bearing request to a member that doesn't actually support vision. Routing now requires `supportsVision === true` (plus a model-id heuristic) before sending images to a target, so multimodal requests land only on members that can handle them. ([#4071](https://github.com/diegosouzapw/OmniRoute/pull/4071) — thanks @diego-anselmo) -- **fix(security): injection guard respects the `INJECTION_GUARD_MODE` DB feature flag** — the prompt-injection guard ignored the database feature-flag, so operators couldn't change its mode at runtime; it now reads the flag and honors the configured mode. ([#4077](https://github.com/diegosouzapw/OmniRoute/pull/4077) — thanks @zhiru) -- **fix(ws): proxy LAN `/live-ws` upgrades and warn on an unset `JWT_SECRET`** — WebSocket upgrade requests arriving over the LAN proxy path were not forwarded to the LiveWS sidecar; they are now proxied correctly, and the server logs a clear warning when `JWT_SECRET` is unset. ([#4079](https://github.com/diegosouzapw/OmniRoute/pull/4079) — thanks @Rahulsharma0810) -- **fix(dev): force webpack in the custom dev server (Turbopack 16.2.x panics)** — the custom dev server now forces the webpack engine because Turbopack panics on this Next.js version, so `npm run dev` boots reliably. ([#4092](https://github.com/diegosouzapw/OmniRoute/pull/4092) — thanks @chirag127) -- **fix(auto): resolve built-in `auto/*` catalog combos** — referencing a built-in `auto/*` combo returned a premature `400` because the catalog entry wasn't resolved; the built-in auto catalog is now resolved before validation so those combos work. ([#4058](https://github.com/diegosouzapw/OmniRoute/pull/4058) — thanks @megamen32) -- **fix(sse): friendly 413 message for ChatGPT-web payload-too-large** — an oversized ChatGPT-web payload returned an opaque error; it now returns a clear `413` with a human-readable message. ([#4080](https://github.com/diegosouzapw/OmniRoute/pull/4080) — thanks @diegosouzapw) -- **fix(ws): warm the SSE auth import on LiveWS startup; relocate the boot test to integration** — the LiveWS sidecar now pre-imports the SSE auth module at startup to avoid a first-request stall, and its boot test was moved to the integration suite. ([#4063](https://github.com/diegosouzapw/OmniRoute/pull/4063) — thanks @diegosouzapw) -- **fix(mitm): crash-safe system-state teardown + socket timeouts (ProxyBridge-inspired hardening)** — the MITM proxy could leave the host's system proxy settings applied if it crashed mid-teardown, and long-lived tunnels could leak as half-open sockets. Teardown is now crash-safe (system state is always restored) and proxied sockets get an idle timeout (`MITM_IDLE_TIMEOUT_MS`, default 60s). ([#4084](https://github.com/diegosouzapw/OmniRoute/pull/4084) — thanks @diegosouzapw) -- **fix(responses): clear the `/v1/responses` keep-alive timer on cancel/abort** — a cancelled or aborted `/v1/responses` stream left its keep-alive timer running, leaking a timer and burning CPU; the timer is now cleared on cancel/abort. ([#4105](https://github.com/diegosouzapw/OmniRoute/pull/4105) — thanks @artickc) -- **fix(usage): reap orphaned pending-request details (unbounded memory leak)** — pending-request detail entries whose request never completed accumulated without bound; they are now reaped, closing a slow memory leak. ([#4107](https://github.com/diegosouzapw/OmniRoute/pull/4107) — thanks @artickc) -- **fix(auth): prune expired entries from the login brute-force guard map (unbounded growth)** — the login brute-force guard map grew without bound because expired entries were never removed; expired entries are now pruned. ([#4111](https://github.com/diegosouzapw/OmniRoute/pull/4111) — thanks @artickc) -- **fix(logger): hard-cap the error-dedup map to bound memory under unique-message bursts** — a burst of unique error messages could grow the dedup map without limit; it is now hard-capped. ([#4113](https://github.com/diegosouzapw/OmniRoute/pull/4113) — thanks @artickc) -- **fix(circuit-breaker): enforce `MAX_REGISTRY_SIZE` (declared but never applied)** — the circuit-breaker registry declared a maximum size that was never enforced, so it could grow unbounded; the cap is now applied. ([#4114](https://github.com/diegosouzapw/OmniRoute/pull/4114) — thanks @artickc) -- **fix(webhook): clear the abort timer in `finally` to avoid dangling timers on fetch error** — a webhook dispatch that threw before clearing its abort timer left the timer dangling; it is now cleared in a `finally` block. ([#4115](https://github.com/diegosouzapw/OmniRoute/pull/4115) — thanks @artickc) -- **fix(combo): detach the per-target listener from the shared hedge abort signal** — combo hedging attached a per-target listener to a shared abort signal without detaching it, leaking listeners across requests; the listener is now detached. ([#4116](https://github.com/diegosouzapw/OmniRoute/pull/4116) — thanks @artickc) -- **fix(timers): unref background interval timers so they don't block clean shutdown** — long-lived background interval timers kept the event loop alive and blocked a clean process exit; they are now `unref`'d. ([#4117](https://github.com/diegosouzapw/OmniRoute/pull/4117) — thanks @artickc) +- **fix(ws): start the LiveWS sidecar with `cwd` at the package root (global/systemd installs)** — 独立 LiveWS 启动器(`scripts/start-ws-server.mjs`)通过 `node --import tsx ` 重新启动自身,但未设置 `cwd`。当 WebSocket sidecar 从包目录外部启动时 — 例如全局 npm/homebrew 安装,或 `systemd`/`launchd` 单元从 `$HOME` 启动 — Node 无法解析 `tsx` 包(`ERR_MODULE_NOT_FOUND: Cannot find package 'tsx'`),即便在包目录下 `tsx` 也无法解析 tsconfig 的 `@/*` 路径别名(如 `@/types/databaseSettings`),导致 sidecar 无法启动。spawn 现在将 `cwd` 固定为包根目录(`scripts/` 的上级目录,`package.json` + `tsconfig.json` 所在位置),从而无论从何处启动,都能解析 `tsx` 和 `@/*` 别名。([#4055](https://github.com/diegosouzapw/OmniRoute/issues/4055) — 感谢 @Rahulsharma0810) +- **fix(dashboard): Logs page auto-refresh now works in embedded/proxied dashboards** — 请求日志器曾将每次自动刷新 tick 依赖于静态的 `document.visibilityState === "visible"` 读取。那些报告永久非 `"visible"` 状态且从不触发 `visibilitychange` 事件的环境(Docker 控制台包装器、嵌入式 webview)会导致自动刷新完全冻结 — 只有手动刷新按钮有效,这是 3.8.24 无条件轮询的回归。暂停现在采用事件驱动和 fail-open 模式:轮询以启用状态启动,仅在真实的 `visibilitychange` → hidden 转换后才暂停(仍然保留对普通浏览器标签页的后台优化)。([#4054](https://github.com/diegosouzapw/OmniRoute/issues/4054) — 感谢 @tjengbudi) +- **fix(docker): raise the build-stage Node heap to stop the production-build OOM** — Docker `builder` 阶段运行 `npm run build` 时使用 V8 的默认堆上限(~2 GB)。自 #4052 强制使用更重的 webpack 引擎后(Turbopack 在此 Next.js 版本上会 panic),生产优化阶段超过了该上限,构建在 `[builder] npm run build` 处以 `FATAL ERROR: … JavaScript heap out of memory` 失败。builder 阶段现在在构建前设置 `NODE_OPTIONS=--max-old-space-size`(默认 4096 MB,可通过 `--build-arg OMNIROUTE_BUILD_MEMORY_MB=…` 覆盖);该值会传递到启动的 `next build`。仅影响构建 — 运行时堆(runner 阶段的 `OMNIROUTE_MEMORY_MB`)保持不变。([#4076](https://github.com/diegosouzapw/OmniRoute/issues/4076) — 感谢 @kamenkadmitry) +- **fix(dashboard): "Update Available" banner reappears reliably across Docker/npm/desktop installs** — 首页 banner 依赖于 `GET /api/system/version` 的 `updateAvailable`,该值仅通过 `npm` CLI 二进制文件执行 `npm info omniroute version --json` 来获取最新版本。当该二进制文件在运行时 PATH 中不存在(Docker/desktop/受限安装)或注册表不可达时,调用返回 `null` → `updateAvailable=false` → banner 静默地从不显示,即便已有更新的版本。路由现在通过 `resolveLatestVersion()` 解析最新版本:优先走快速的 `npm` CLI 路径,然后通过注册表 HTTP API(`registry.npmjs.org/omniroute/latest`)进行无 npm 二进制的容灾,两者都失败时记录警告而非静默降级。版本比较也经过强化,能容忍 `v` 前缀和预发布版本字符串。([#4100](https://github.com/diegosouzapw/OmniRoute/issues/4100)) +- **fix(sse): route image requests only to confirmed-vision combo targets** — Combo 可能会将包含图像的请求路由到不支持视觉功能的成员。路由现在要求 `supportsVision === true`(加上模型 ID 启发式检查)后才向目标发送图像,确保多模态请求只落在能处理它们的成员上。([#4071](https://github.com/diegosouzapw/OmniRoute/pull/4071) — 感谢 @diego-anselmo) +- **fix(security): injection guard respects the `INJECTION_GUARD_MODE` DB feature flag** — 提示注入防护曾忽略数据库功能标志,导致运维人员无法在运行时更改其模式;现在它会读取该标志并遵循配置的模式。([#4077](https://github.com/diegosouzapw/OmniRoute/pull/4077) — 感谢 @zhiru) +- **fix(ws): proxy LAN `/live-ws` upgrades and warn on an unset `JWT_SECRET`** — 通过 LAN 代理路径到达的 WebSocket 升级请求未被转发到 LiveWS sidecar;现在已正确代理,且服务器在 `JWT_SECRET` 未设置时会记录明确的警告。([#4079](https://github.com/diegosouzapw/OmniRoute/pull/4079) — 感谢 @Rahulsharma0810) +- **fix(dev): force webpack in the custom dev server (Turbopack 16.2.x panics)** — 自定义 dev server 现在强制使用 webpack 引擎,因为 Turbopack 在此 Next.js 版本上会 panic,从而确保 `npm run dev` 可靠启动。([#4092](https://github.com/diegosouzapw/OmniRoute/pull/4092) — 感谢 @chirag127) +- **fix(auto): resolve built-in `auto/*` catalog combos** — 引用内置 `auto/*` Combo 时返回过早的 `400`,因为目录条目未被解析;内置 auto 目录现在在验证前完成解析,使这些 combo 正常工作。([#4058](https://github.com/diegosouzapw/OmniRoute/pull/4058) — 感谢 @megamen32) +- **fix(sse): friendly 413 message for ChatGPT-web payload-too-large** — 过大的 ChatGPT-web 载荷曾返回模糊的错误;现在返回清晰的 `413` 和人类可读的消息。([#4080](https://github.com/diegosouzapw/OmniRoute/pull/4080) — 感谢 @diegosouzapw) +- **fix(ws): warm the SSE auth import on LiveWS startup; relocate the boot test to integration** — the LiveWS sidecar now pre-imports the SSE 认证 module at startup to avoid a first-请求 stall, and its boot test was moved to the 集成 suite. ([#4063](https://github.com/diegosouzapw/OmniRoute/pull/4063) — 感谢 @diegosouzapw) +- **fix(mitm): crash-safe system-state teardown + socket timeouts (ProxyBridge-inspired hardening)** — the MITM 代理 could leave the host's system 代理 settings applied if it crashed mid-teardown, and long-lived tunnels could leak as half-open sockets. Teardown 现在 crash-safe (system state is always restored) and proxied sockets get an idle timeout (`MITM_IDLE_TIMEOUT_MS`, 默认 60s). ([#4084](https://github.com/diegosouzapw/OmniRoute/pull/4084) — 感谢 @diegosouzapw) +- **fix(responses): clear the `/v1/responses` keep-alive timer on cancel/abort** — 被取消或中止的 `/v1/responses` 流曾遗留 keep-alive 定时器运行,导致定时器泄漏和 CPU 浪费;定时器现在在 cancel/abort 时被清除。([#4105](https://github.com/diegosouzapw/OmniRoute/pull/4105) — 感谢 @artickc) +- **fix(usage): reap orphaned pending-request details (unbounded memory leak)** — 请求永不完成的待处理请求详情条目无限累积;现在它们被回收,修复了缓慢的内存泄漏。([#4107](https://github.com/diegosouzapw/OmniRoute/pull/4107) — 感谢 @artickc) +- **fix(auth): prune expired entries from the login brute-force guard map (unbounded growth)** — 登录暴力破解防护映射因过期条目从未被移除而无限制增长;过期条目现在被清理。([#4111](https://github.com/diegosouzapw/OmniRoute/pull/4111) — 感谢 @artickc) +- **fix(logger): hard-cap the error-dedup map to bound memory under unique-message bursts** — 唯一错误消息的突发可能导致去重映射无限制增长;现在已硬性限制上限。([#4113](https://github.com/diegosouzapw/OmniRoute/pull/4113) — 感谢 @artickc) +- **fix(circuit-breaker): enforce `MAX_REGISTRY_SIZE` (declared but never applied)** — 熔断器注册表声明了最大大小但从未强制执行,可能导致无限制增长;上限现在已生效。([#4114](https://github.com/diegosouzapw/OmniRoute/pull/4114) — 感谢 @artickc) +- **fix(webhook): clear the abort timer in `finally` to avoid dangling timers on fetch error** — webhook 分发在清除 abort 定时器之前抛出异常时,定时器会悬空;现在在 `finally` 块中清除。([#4115](https://github.com/diegosouzapw/OmniRoute/pull/4115) — 感谢 @artickc) +- **fix(combo): detach the per-target listener from the shared hedge abort signal** — Combo hedging 将每个目标的监听器附加到共享的 abort 信号上但未分离,导致跨请求的监听器泄漏;监听器现在被分离。([#4116](https://github.com/diegosouzapw/OmniRoute/pull/4116) — 感谢 @artickc) +- **fix(timers): unref background interval timers so they don't block clean shutdown** — 长时间运行的后台间隔定时器保持事件循环活跃,阻止了干净的进程退出;它们现在被 `unref` 了。([#4117](https://github.com/diegosouzapw/OmniRoute/pull/4117) — 感谢 @artickc) -### ⚡ Performance +### ⚡ 性能优化 -- **perf(registry): precompute the model→provider index in `parseModelFromRegistry`** — model→provider lookups now use a precomputed index instead of scanning the registry on every call. ([#4110](https://github.com/diegosouzapw/OmniRoute/pull/4110) — thanks @artickc) -- **perf(obfuscation): cache per-word regexes instead of recompiling every request** — the obfuscation pass now caches its per-word regexes rather than recompiling them on each request. ([#4109](https://github.com/diegosouzapw/OmniRoute/pull/4109) — thanks @artickc) -- **perf(stream): use `structuredClone` instead of a JSON round-trip for per-chunk reasoning split** — the per-chunk reasoning split now clones with `structuredClone` rather than `JSON.parse(JSON.stringify(...))`. ([#4108](https://github.com/diegosouzapw/OmniRoute/pull/4108) — thanks @artickc) -- **perf(gemini): cache the reasoning close-tag regex instead of recompiling per token** — the Gemini reasoning close-tag regex is now compiled once and reused instead of per token. ([#4106](https://github.com/diegosouzapw/OmniRoute/pull/4106) — thanks @artickc) +- **perf(registry): precompute the model→provider index in `parseModelFromRegistry`** — 模型→服务商查找现在使用预计算索引,而非每次调用时扫描注册表。([#4110](https://github.com/diegosouzapw/OmniRoute/pull/4110) — 感谢 @artickc) +- **perf(obfuscation): cache per-word regexes instead of recompiling every request** — 混淆阶段现在缓存每个单词的正则表达式,而非每次请求时重新编译。([#4109](https://github.com/diegosouzapw/OmniRoute/pull/4109) — 感谢 @artickc) +- **perf(stream): use `structuredClone` instead of JSON round-trip for per-chunk reasoning split** — 每个 chunk 的推理分割现在使用 `structuredClone` 而非 `JSON.parse(JSON.stringify(...))`。([#4108](https://github.com/diegosouzapw/OmniRoute/pull/4108) — 感谢 @artickc) +- **perf(gemini): cache the reasoning close-tag regex instead of recompiling per token** — Gemini 推理关闭标签的正则表达式现在只编译一次并复用,而非每个 token 都重新编译。([#4106](https://github.com/diegosouzapw/OmniRoute/pull/4106) — 感谢 @artickc) -### 📝 Maintenance +### 📝 维护 -- **ci(quality): flip the TIA impacted-unit-tests gate from advisory to blocking (Fase 9)** — the test-impact-analysis gate that runs the unit tests impacted by a diff is now blocking on PRs. ([#4069](https://github.com/diegosouzapw/OmniRoute/pull/4069) — thanks @diegosouzapw) -- **ci(quality): dedup the doubly-run `check:docs-sync` + record the validated ROI backlog (Fase 9)** — `check:docs-sync` was running twice in CI; the duplicate was removed and the validated quality-gate ROI backlog recorded. ([#4099](https://github.com/diegosouzapw/OmniRoute/pull/4099) — thanks @diegosouzapw) -- **docs(quality-gates): reconcile the gate inventory with `ci.yml` + add the ROI rationalization backlog** — the quality-gate inventory doc was reconciled against the actual CI jobs and a rationalization backlog added. ([#4095](https://github.com/diegosouzapw/OmniRoute/pull/4095) — thanks @diegosouzapw) -- **test(infra): isolate `DATA_DIR` per test process; raise Stryker concurrency 1→4** — test processes now get an isolated `DATA_DIR` (no shared-DB cross-talk) and the mutation runner's concurrency was raised. ([#4078](https://github.com/diegosouzapw/OmniRoute/pull/4078) — thanks @diegosouzapw) -- **test(dashboard): smoke e2e for the Combo Live Studio page** — adds a Playwright smoke test covering the Combo Live Studio page. ([#4075](https://github.com/diegosouzapw/OmniRoute/pull/4075) — thanks @diegosouzapw) -- **docs(compression): document LLMLingua optional deps + on-demand install** — documents the optional LLMLingua dependencies and how they are installed on demand. ([#4061](https://github.com/diegosouzapw/OmniRoute/pull/4061) — thanks @diegosouzapw) -- **chore(deps): freeze `@huggingface/transformers` in dependabot (hard-pin)** — the transformers dependency is hard-pinned and frozen in dependabot to protect the VPS-validated LLMLingua + memory-embeddings stack from a breaking major bump. ([#4066](https://github.com/diegosouzapw/OmniRoute/pull/4066) — thanks @diegosouzapw) -- **chore(docs): update the Discord invite link to a non-expiring one** — replaces the expiring Discord invite with a permanent link. ([#4067](https://github.com/diegosouzapw/OmniRoute/pull/4067) — thanks @diegosouzapw) -- **chore(docs): document the new MITM env vars + reconcile the env-doc contract** — documents `MITM_IDLE_TIMEOUT_MS` and `MITM_VERBOSE` in `.env.example` + `ENVIRONMENT.md`, allowlists the framework-internal `TURBOPACK` and the Claude Code `ANTHROPIC_AUTH_TOKEN`, and relocates/prunes stale provider/guide docs. (thanks @diegosouzapw) +- **ci(quality): flip the TIA impacted-unit-tests gate from advisory to blocking (Fase 9)** — the test-impact-analysis gate that runs the unit tests impacted by a diff 现在 blocking on PRs. ([#4069](https://github.com/diegosouzapw/OmniRoute/pull/4069) — 感谢 @diegosouzapw) +- **ci(quality): dedup the doubly-run `check:docs-sync` + record the validated ROI backlog (Fase 9)** — `check:docs-sync` was running twice in CI; the duplicate was removed and the validated quality-gate ROI backlog recorded. ([#4099](https://github.com/diegosouzapw/OmniRoute/pull/4099) — 感谢 @diegosouzapw) +- **docs(quality-gates): reconcile the gate inventory with `ci.yml` + add the ROI rationalization backlog** — the quality-gate inventory doc was reconciled against the actual CI jobs and a rationalization backlog added. ([#4095](https://github.com/diegosouzapw/OmniRoute/pull/4095) — 感谢 @diegosouzapw) +- **test(infra): isolate `DATA_DIR` per test process; raise Stryker concurrency 1→4** — test processes now get an isolated `DATA_DIR` (no shared-DB cross-talk) and the mutation runner's concurrency was raised. ([#4078](https://github.com/diegosouzapw/OmniRoute/pull/4078) — 感谢 @diegosouzapw) +- **test(dashboard): smoke e2e for the Combo Live Studio page** — adds a Playwright smoke test covering the Combo Live Studio 页面. ([#4075](https://github.com/diegosouzapw/OmniRoute/pull/4075) — 感谢 @diegosouzapw) +- **docs(compression): document LLMLingua optional deps + on-demand install** — documents the optional LLMLingua dependencies and how they are installed on demand. ([#4061](https://github.com/diegosouzapw/OmniRoute/pull/4061) — 感谢 @diegosouzapw) +- **chore(deps): freeze `@huggingface/transformers` in dependabot (hard-pin)** — the transformers dependency is hard-pinned and frozen in dependabot to protect the VPS-validated LLMLingua + memory-embeddings stack from a breaking major bump. ([#4066](https://github.com/diegosouzapw/OmniRoute/pull/4066) — 感谢 @diegosouzapw) +- **chore(docs): update the Discord invite link to a non-expiring one** — replaces the expiring Discord invite with a permanent link. ([#4067](https://github.com/diegosouzapw/OmniRoute/pull/4067) — 感谢 @diegosouzapw) +- **chore(docs): document the new MITM env vars + reconcile the env-doc contract** — documents `MITM_IDLE_TIMEOUT_MS` and `MITM_VERBOSE` in `.env.example` + `ENVIRONMENT.md`, allowlists the framework-internal `TURBOPACK` and the Claude Code `ANTHROPIC_AUTH_TOKEN`, and relocates/prunes stale 服务商/guide docs. (thanks @diegosouzapw) -### 🔧 Dependencies +### 🔧 依赖 - **deps: bump the development group with 10 updates** — routine dependabot dev-dependency bumps. ([#4051](https://github.com/diegosouzapw/OmniRoute/pull/4051)) - **deps(electron): bump electron 42.4.0 → 42.4.1** — ([#4049](https://github.com/diegosouzapw/OmniRoute/pull/4049)) @@ -1404,186 +1422,186 @@ _See English CHANGELOG for v3.8.32 details._ ## [3.8.27] — 2026-06-17 -### ✨ New Features +### ✨ 新功能 -- **feat(combos): advertise combo capabilities (multimodal / reasoning / caching) on the import surfaces** — importing a combo package into a client (LobeHub / OpenCode / VS Code, via `/v1/combos` and the VS Code combo catalog) no longer requires manually enabling multimodal/image-input, reasoning, and caching afterwards. `projectCombo` now attaches a registry-derived `capabilities` block, gated conservatively: `multimodal`/`reasoning` are advertised only when **every** concrete model step proves the capability (an unprovable nested combo-ref drops them, since the strategy may route to any member), and `caching` reflects the combo's explicit Context-Cache-Protection setting (no surprise prompt-cache cost). The public `/v1/combos` default projection (#2300) is unchanged unless the caller opts in. ([#3979](https://github.com/diegosouzapw/OmniRoute/issues/3979) — thanks @xenstar) -- **feat(sse): delegated Anthropic Context Editing for Claude (`clear_tool_uses`)** — Claude requests can now offload context trimming to Anthropic's server-side context-management API (beta `context-management-2025-06-27`, `clear_tool_uses_20250919`), pruning stale tool-use turns upstream instead of locally. Claude-only by nature (the edit runs server-side); multi-provider context trimming remains the job of the local compression engines. ([#4021](https://github.com/diegosouzapw/OmniRoute/pull/4021) — thanks @diegosouzapw) -- **feat(sse): real LLMLingua-2 ONNX compression engine (stable)** — the LLMLingua-2 prompt-compression engine is now a real local ONNX model (TinyBERT default, transformers.js + tfjs), promoted to stable after VPS validation, replacing the previous placeholder. ([#4014](https://github.com/diegosouzapw/OmniRoute/pull/4014) — thanks @diegosouzapw) -- **feat(compression): capture per-engine analytics + Lite schema fix** — the compression pipeline now persists a per-engine breakdown for historical analytics so the dashboard can attribute savings to each engine in a stacked pipeline, and a Lite-schema mismatch was corrected. ([#4018](https://github.com/diegosouzapw/OmniRoute/pull/4018) — thanks @diegosouzapw) -- **feat(dashboard): real circuit-breaker state in the Combo Live cascade (U1b)** — the Combo Live cascade view now surfaces each provider's real circuit-breaker state (CLOSED / OPEN / HALF_OPEN) as a badge, read live from `/api/monitoring/health`, instead of inferring health from request outcomes. ([#4029](https://github.com/diegosouzapw/OmniRoute/pull/4029) — thanks @diegosouzapw) -- **feat(openai): honor a custom base URL in model discovery + complete openai/codex pricing** — OpenAI-format providers configured with a custom base URL now have that URL honored during model discovery (not just inference), and the openai/codex pricing table was completed. Discovery is routed through the SSRF-guarded outbound fetch. ([#4005](https://github.com/diegosouzapw/OmniRoute/pull/4005) — thanks @artickc) -- **feat(observability): capture actual upstream provider requests** — the request inspector now records the exact payload sent to the upstream provider (post-translation), so you can see what OmniRoute actually dispatched rather than only the client's original request. ([#3941](https://github.com/diegosouzapw/OmniRoute/pull/3941) — thanks @rdself) -- **feat(providers): provider auth visibility controls** — adds controls to show/hide provider auth details in the dashboard so credentials can be revealed only when needed. ([#3953](https://github.com/diegosouzapw/OmniRoute/pull/3953) — thanks @rdself) -- **feat(providers): model search filter on the provider dashboard** — the provider dashboard gains a search filter to quickly narrow a provider's model list. ([#3950](https://github.com/diegosouzapw/OmniRoute/pull/3950) — thanks @felipesartori) -- **feat(compression): Indonesian caveman rules + language pack** — adds an Indonesian "caveman" rule set and language pack to the rule-based compression engine. ([#3975](https://github.com/diegosouzapw/OmniRoute/pull/3975) — thanks @Veier04) -- **feat(dashboard): sidebar group separator toggles** — the dashboard sidebar can now toggle group separators for a cleaner navigation layout. ([#3971](https://github.com/diegosouzapw/OmniRoute/pull/3971) — thanks @rdself) -- **feat(api): local `@@om-usage` command for cached per-key usage** — API clients can send a message that is exactly `@@om-usage` to retrieve cached Claude-style usage data locally, without forwarding the prompt to an upstream provider. Gated by a new per-key allowance flag. ([#4034](https://github.com/diegosouzapw/OmniRoute/pull/4034) — thanks @Witroch4) +- **feat(combos): 在导入界面宣传 Combo 能力(多模态/推理/缓存)** — 将 Combo 包导入客户端(LobeHub / OpenCode / VS Code,通过 `/v1/combos` 和 VS Code Combo 目录)后不再需要手动启用多模态/图像输入、推理和缓存。`projectCombo` 现在附加注册表派生的 `capabilities` 块,采用保守策略:仅当**所有**具体模型步骤均证明该能力时才声明 `multimodal`/`reasoning`(无法证明的嵌套 Combo 引用会丢弃这些能力,因为策略可能路由到任意成员),`caching` 反映 Combo 显式的上下文缓存保护设置(不会产生意外的提示缓存成本)。公开的 `/v1/combos` 默认映射 (#2300) 保持不变,除非调用者主动选择。([#3979](https://github.com/diegosouzapw/OmniRoute/issues/3979) — 感谢 @xenstar) +- **feat(sse): 委托 Anthropic 上下文编辑支持 Claude (`clear_tool_uses`)** — Claude 请求现在可以将上下文裁剪卸载到 Anthropic 的服务端上下文管理 API(beta `context-management-2025-06-27`、`clear_tool_uses_20250919`),在上游而非本地修剪过期的工具调用轮次。该功能仅适用于 Claude(编辑在服务端执行);多服务商上下文裁剪仍由本地压缩引擎处理。([#4021](https://github.com/diegosouzapw/OmniRoute/pull/4021) — 感谢 @diegosouzapw) +- **feat(sse): 真实的 LLMLingua-2 ONNX 压缩引擎(稳定版)** — LLMLingua-2 提示压缩引擎现在使用真实的本地 ONNX 模型(默认 TinyBERT、transformers.js + tfjs),经 VPS 验证后提升为稳定版,替换了之前的占位实现。([#4014](https://github.com/diegosouzapw/OmniRoute/pull/4014) — 感谢 @diegosouzapw) +- **feat(compression): 捕获每个引擎的分析数据 + Lite schema 修复** — 压缩管线现在为历史分析持久化每个引擎的详细数据,使控制台能将节省量归因到堆叠管线中的每个引擎,同时修复了 Lite schema 不匹配的问题。([#4018](https://github.com/diegosouzapw/OmniRoute/pull/4018) — 感谢 @diegosouzapw) +- **feat(dashboard): Combo Live 级联中显示真实熔断器状态 (U1b)** — Combo Live 级联视图现在将每个服务商的真实熔断器状态(CLOSED / OPEN / HALF_OPEN)以徽章形式展示,从 `/api/monitoring/health` 实时读取,而非从请求结果推断健康状态。([#4029](https://github.com/diegosouzapw/OmniRoute/pull/4029) — 感谢 @diegosouzapw) +- **feat(openai): 模型发现时遵循自定义 base URL + 补全 openai/codex 定价** — 配置了自定义 base URL 的 OpenAI 格式服务商现在在模型发现(不仅是推理)时也遵循该 URL,openai/codex 定价表已补全。发现请求通过 SSRF 保护的外部 fetch 路由。([#4005](https://github.com/diegosouzapw/OmniRoute/pull/4005) — 感谢 @artickc) +- **feat(observability): 捕获实际上游服务商请求** — 请求检查器现在记录发送到上游服务商的确切载荷(翻译后),使您能看到 OmniRoute 实际派发的内容,而非仅看到客户端原始请求。([#3941](https://github.com/diegosouzapw/OmniRoute/pull/3941) — 感谢 @rdself) +- **feat(providers): 服务商认证可见性控制** — 添加在控制台中显示/隐藏服务商认证详情的控制,使凭证仅在需要时可见。([#3953](https://github.com/diegosouzapw/OmniRoute/pull/3953) — 感谢 @rdself) +- **feat(providers): 服务商仪表盘的模型搜索过滤器** — 服务商控制台新增搜索过滤器,可快速筛选服务商的模型列表。([#3950](https://github.com/diegosouzapw/OmniRoute/pull/3950) — 感谢 @felipesartori) +- **feat(compression): 印尼语 caveman 规则 + 语言包** — 为基于规则的压缩引擎添加印尼语 "caveman" 规则集和语言包。([#3975](https://github.com/diegosouzapw/OmniRoute/pull/3975) — 感谢 @Veier04) +- **feat(dashboard): 侧边栏分组分隔符开关** — 控制台侧边栏现在可以开关分组分隔符,获得更简洁的导航布局。([#3971](https://github.com/diegosouzapw/OmniRoute/pull/3971) — 感谢 @rdself) +- **feat(api): 本地 `@@om-usage` 命令支持缓存的按 key 用量** — API 客户端可发送恰好为 `@@om-usage` 的消息,在本地检索缓存的 Claude 风格用量数据,无需将 prompt 转发到上游服务商。由新的按 key 许可标志控制。([#4034](https://github.com/diegosouzapw/OmniRoute/pull/4034) — 感谢 @Witroch4) -### 🐛 Fixed +### 🐛 问题修复 -- **fix(opencode): forward the OpenCode session id to the upstream regardless of how the user named the provider** — the `OpencodeExecutor` forwarded the `x-opencode-session/request/project/client` headers, but the OpenCode CLI only emits those when the configured `providerID` **starts with** `"opencode"`. A user who adds OmniRoute as a custom provider (e.g. `"omniroute"`) makes the CLI send `x-session-affinity` / `X-Session-Id` instead (both carry the same session id), which the executor never read — so the session-metadata forwarding was effectively dead code for the realistic provider-naming case. The opencode-family executor now falls back to `x-session-affinity` / `X-Session-Id` and maps it onto `x-opencode-session` when the client didn't send the header directly, so session continuity to the `opencode.ai` upstream works for any provider name (a direct `x-opencode-session` still wins). Scoped to this executor only — the generic `DefaultExecutor` intentionally does **not** do this, to avoid leaking the client session id to arbitrary third-party upstreams. ([#4022](https://github.com/diegosouzapw/OmniRoute/issues/4022) — thanks @pizzav-xyz) -- **fix(guardrails): Vision Bridge no longer drops the image when the describe call fails (Nvidia NIM "Image unavailable")** — the Vision Bridge is enabled by default and engages for any model whose vision capability OmniRoute can't prove from the registry (`supportsVision !== true`, which includes uncatalogued models that resolve to `null`). When the per-image describe call failed (e.g. no vision model configured), it replaced the image with the literal text `[Image N]: (unavailable)` and dropped the original `image_url` — so a genuinely vision-capable upstream (Nvidia NIM) received text only and answered "Image unavailable. Cannot provide description without visual data." A describe failure is no longer destructive: `replaceImageParts` now receives `null` for failed images and **preserves the original image part** so the upstream can still see it (successful describes still replace the image with the text description; `meta.descriptions` observability is unchanged). ([#4012](https://github.com/diegosouzapw/OmniRoute/issues/4012) — thanks @daniij) -- **fix(kiro): preserve `finish_reason: "tool_calls"` on the Kiro streaming path** — streaming tool-call requests through the Kiro (Responses API) provider had their terminal `finish_reason` reported as `"stop"` instead of `"tool_calls"`, so agent clients (Hermes) treated the tool-call turn as a finished turn, never ran the tool, and the next request failed with HTTP 400 on the incomplete tool state. `convertKiroToOpenAI`'s terminal `messageStopEvent`/`done` branch hardcoded `finish_reason: "stop"` regardless of whether the stream had emitted `toolUseEvent`s. The translator now records `state.sawToolUse` when a tool-use chunk is emitted and reports `finish_reason: "tool_calls"` on the terminal chunk (and in `state.finishReason`) whenever the stream produced tool calls. The non-streaming path was already correct. ([#3980](https://github.com/diegosouzapw/OmniRoute/issues/3980) — thanks @lordavadon2) -- **fix(resilience): respect connection cooldown stored as a numeric epoch** — the router kept dispatching to connections still inside their rate-limit cooldown because `rate_limited_until` (a `TEXT` column) was persisted as a raw epoch number, which SQLite coerced to a string like `"1781696905131.0"` that `new Date(...)` parsed as `NaN`, so the cooling connection was never skipped. The cooldown read predicates now normalize numeric-epoch strings via a shared `cooldownUntilMs()` helper; ISO behavior is unchanged. ([#3995](https://github.com/diegosouzapw/OmniRoute/pull/3995) — thanks @diegosouzapw) -- **fix(providers): fetch the live `/models` catalog for LLM7 and BytePlus** — importing an LLM7 or BytePlus key surfaced only a small, outdated hardcoded list because neither provider was classified by any live-fetch branch of the model-import route. Both are now in `NAMED_OPENAI_STYLE_PROVIDERS`, so the route probes `/models` with the key and serves the live catalog, falling back to the local catalog only when the upstream fetch fails. ([#3996](https://github.com/diegosouzapw/OmniRoute/pull/3996) — thanks @FerLuisxd / @diegosouzapw) -- **fix(dashboard): logs auto-refresh reads live visibility, not a stale mount ref** — the Logs page never auto-refreshed when the tab loaded in the background because the auto-refresh interval gated each tick on a visibility ref seeded once at mount; the tick now reads the live `document.visibilityState`, so polling self-heals as soon as the tab is visible while still pausing when genuinely hidden. ([#3997](https://github.com/diegosouzapw/OmniRoute/pull/3997) — thanks @tjengbudi / @diegosouzapw) -- **fix(combo): shuffle the strict-random fallback remainder to spread load** — with the `strict-random` strategy a persistently-failing model was retried on essentially every request because only the deck-selected slot 0 was shuffled while the fallback remainder stayed in fixed priority order; the remainder is now shuffled too, so fallback load (and recovery from a failing target) spreads evenly across healthy peers. ([#3998](https://github.com/diegosouzapw/OmniRoute/pull/3998) — thanks @KeNJiKunG / @diegosouzapw) -- **fix(claude): forward the client `tool-search-tool-2025-10-19` anthropic-beta on the Claude OAuth path** — with deferred tools active, Claude Code negotiates the `tool-search-tool-2025-10-19` beta, but OmniRoute dropped it on both Claude code paths, so the claude.ai backend rejected every deferred-tool request with `400 Tool reference not found`. A new allowlist-merge (`mergeClientAnthropicBeta`) now unions the client's negotiated beta into the outbound set on both paths, appending only allowlisted client betas (preserving the #3415 fix). ([#3999](https://github.com/diegosouzapw/OmniRoute/pull/3999) — thanks @huohua-dev / @diegosouzapw) -- **fix(executor): strip `stream_options` on non-streaming requests (NVIDIA NIM 400)** — clients that send `stream_options: { include_usage: true }` regardless of `stream` (e.g. the OpenAI Python SDK) had it passed through untouched on non-streaming calls, and NVIDIA NIM rejected it with `400 "Stream options can only be defined when stream=True"`. `DefaultExecutor.transformRequest` now strips `stream_options` whenever `stream` is false; the streaming injection path is unchanged. ([#4000](https://github.com/diegosouzapw/OmniRoute/pull/4000) — thanks @andrea-kingautomation / @daniij / @diegosouzapw) -- **fix(sse): guard model-less registry entries in `getUnsupportedParams` (mimocode)** — a registry entry without a model map (mimocode) threw when computing unsupported params; the lookup now guards the model-less case so request validation no longer crashes. ([#4015](https://github.com/diegosouzapw/OmniRoute/pull/4015) — thanks @diegosouzapw) -- **fix(perplexity-web): parse the schematized `diff_block` stream so answers aren't empty** — Perplexity web streamed its answer as RFC-6902 `diff_block` patches that OmniRoute didn't apply during the `PENDING` phase, so responses came back empty; the parser now applies the patches and materializes the text only on `COMPLETED`. ([#4001](https://github.com/diegosouzapw/OmniRoute/pull/4001) — thanks @artickc) -- **fix(default-executor): honor a custom `providerSpecificData.baseUrl` for OpenAI-format providers** — OpenAI-format providers configured with a custom base URL had it ignored on the inference path; the default executor now honors `providerSpecificData.baseUrl` so requests reach the configured endpoint. ([#4002](https://github.com/diegosouzapw/OmniRoute/pull/4002) — thanks @artickc) -- **fix(live-ws): bridge LiveWS sidecar events to the dashboard** — events emitted by the LiveWS sidecar were not reaching the dashboard; they are now bridged so live websocket activity is visible. (A cookie-auth regression in the sidecar's auth-token parsing was also corrected.) ([#4004](https://github.com/diegosouzapw/OmniRoute/pull/4004) — thanks @megamen32) -- **fix(qwen-web): cookie validation false-positive — check the response body for a user object** — Qwen web cookie validation reported a valid cookie as invalid; it now inspects the response body for the `user` object instead of relying on the status code alone. ([#3958](https://github.com/diegosouzapw/OmniRoute/pull/3958) — thanks @thezukiru) -- **fix(vision-bridge): force the bridge for tokenrouter deepseek models** — tokenrouter DeepSeek models are now forced through the Vision Bridge so image inputs are handled correctly. ([#3946](https://github.com/diegosouzapw/OmniRoute/pull/3946) — thanks @WormAlien) -- **fix(api): return 400 (not 500) for malformed JSON on `/api/auth/login`** — a malformed JSON body on the login endpoint returned an opaque 500; it now returns a proper 400. ([#4031](https://github.com/diegosouzapw/OmniRoute/pull/4031) — thanks @rdself) -- **fix(dashboard): Playground Compare tab loading + HTTP method guard** — the Playground Compare tab failed to load; the loading path was fixed and an HTTP method guard added. ([#4024](https://github.com/diegosouzapw/OmniRoute/pull/4024) — thanks @rdself) -- **fix(proxy): gate the control-plane proxy direct fallback behind a feature flag (fail-closed)** — the direct-connection fallback for control-plane ops when a pinned proxy is unreachable is now gated behind a feature flag and fails closed, so a pinned proxy is never silently bypassed unless explicitly allowed. ([#3963](https://github.com/diegosouzapw/OmniRoute/pull/3963) — thanks @rdself) -- **fix(db): persist backup retention days** — the backup retention-days setting was not persisted across restarts; it is now stored durably. ([#3970](https://github.com/diegosouzapw/OmniRoute/pull/3970) — thanks @rdself) -- **fix(dashboard): refine the provider quota card display** — the provider quota card layout was refined for clearer quota/usage presentation. ([#3969](https://github.com/diegosouzapw/OmniRoute/pull/3969) — thanks @rdself) -- **fix(dashboard): refine compression settings, storage labels, and sidebar grouping** — polishes the compression-settings UI, clarifies storage labels, and tidies the sidebar grouping. ([#4033](https://github.com/diegosouzapw/OmniRoute/pull/4033) — thanks @rdself) +- **fix(opencode): 无论用户如何命名服务商,都将 OpenCode 会话 ID 转发到上游** — `OpencodeExecutor` 转发了 `x-opencode-会话/请求/project/client` 头,但 OpenCode CLI 仅在配置的 `providerID` **以** `"opencode"` 开头时才发送这些头。如果用户将 OmniRoute 添加为自定义服务商(如 `"omniroute"`),CLI 会发送 `x-会话-affinity` / `X-Session-Id`(两者携带相同的会话 ID),而执行器从未读取这些头——因此在实际的服务商命名场景下,会话元数据转发实际上是一段死代码。opencode 系列执行器现在回退到 `x-会话-affinity` / `X-Session-Id` 并映射到 `x-opencode-会话`(当客户端未直接发送该头时),使得对 `opencode.ai` 上游的会话连续性对任何服务商名称都有效(直接发送的 `x-opencode-会话` 仍然优先)。仅限于此执行器——通用 `DefaultExecutor` 故意**不**这样做,以避免将客户端会话 ID 泄漏到任意第三方上游。([#4022](https://github.com/diegosouzapw/OmniRoute/issues/4022) — 感谢 @pizzav-xyz) +- **fix(guardrails): Vision Bridge 在 describe 调用失败时不再丢弃图像(Nvidia NIM "Image unavailable")** — Vision Bridge 默认启用,并对 OmniRoute 无法从注册表证明其视觉能力的任何模型生效(`supportsVision !== true`,包括解析为 `null` 的未编目模型)。当每图像 describe 调用失败时(如未配置视觉模型),它用文字 `[Image N]: (unavailable)` 替换图像并丢弃原始 `image_url`——因此真正具有视觉能力的上游(Nvidia NIM)只收到文本并回答 "Image unavailable. Cannot provide description without visual data."。describe 失败不再具有破坏性:`replaceImageParts` 现在对失败图像接收 `null` 并**保留原始图像部分**,使上游仍能看到它(成功的 describe 仍用文本描述替换图像;`meta.descriptions` 可观测性不变)。([#4012](https://github.com/diegosouzapw/OmniRoute/issues/4012) — 感谢 @daniij) +- **fix(kiro): 在 Kiro 流式路径上保留 `finish_reason: "tool_calls"`** — 通过 Kiro (Responses API) 服务商的流式 tool-call 请求,其终端 `finish_reason` 被报告为 `"stop"` 而非 `"tool_calls"`,因此 Agent 客户端(Hermes)将 tool-call 轮次视为已完成轮次,从未运行工具,下一个请求因不完整的工具状态而失败并返回 HTTP 400。`convertKiroToOpenAI` 的终端 `messageStopEvent`/`done` 分支硬编码了 `finish_reason: "stop"`,无论流是否发出了 `toolUseEvent`。翻译器现在在发出 tool-use 块时记录 `state.sawToolUse`,并在流产生了工具调用时在终端块(和 `state.finishReason`)中报告 `finish_reason: "tool_calls"`。非流式路径此前已正确。([#3980](https://github.com/diegosouzapw/OmniRoute/issues/3980) — 感谢 @lordavadon2) +- **fix(resilience): 正确处理以数字纪元形式存储的连接冷却期** — 路由器持续向仍在速率限制冷却期内的连接派发请求,因为 `rate_limited_until`(`TEXT` 列)以原始纪元数字持久化,SQLite 将其强制转换为字符串如 `"1781696905131.0"`,而 `new Date(...)` 将其解析为 `NaN`,因此冷却中的连接从未被跳过。冷却读取谓词现在通过共享的 `cooldownUntilMs()` 辅助函数规范化数字纪元字符串;ISO 行为不变。([#3995](https://github.com/diegosouzapw/OmniRoute/pull/3995) — 感谢 @diegosouzapw) +- **fix(providers): 为 LLM7 和 BytePlus 获取线上 `/models` 目录** — 导入 LLM7 或 BytePlus key 仅显示一个过时的小型硬编码列表,因为这两个服务商均未被模型导入路由的任何实时获取分支分类。两者现在均加入 `NAMED_OPENAI_STYLE_PROVIDERS`,因此路由使用该 key 探测 `/模型` 并提供线上目录,仅在上游获取失败时回退到本地目录。([#3996](https://github.com/diegosouzapw/OmniRoute/pull/3996) — 感谢 @FerLuisxd / @diegosouzapw) +- **fix(dashboard): 日志自动刷新读取实时可见性状态,而非过时的挂载引用** — 当标签页在后台加载时,日志页面从未自动刷新,因为自动刷新间隔在每个 tick 上受挂载时一次性播种的可见性引用控制;tick 现在读取实时的 `document.visibilityState`,因此轮询在标签页可见后立即自愈,同时真正隐藏时仍然暂停。([#3997](https://github.com/diegosouzapw/OmniRoute/pull/3997) — 感谢 @tjengbudi / @diegosouzapw) +- **fix(combo): 打乱 strict-random 容灾余量以分散负载** — 使用 `strict-random` 策略时,持续失败的模型几乎在每次请求上都重试,因为只有牌组选中的 slot 0 被洗牌,而容灾余量保持固定优先级顺序;余量现在也被洗牌,因此容灾负载(及从失败目标恢复)均匀分散到健康对等节点。([#3998](https://github.com/diegosouzapw/OmniRoute/pull/3998) — 感谢 @KeNJiKunG / @diegosouzapw) +- **fix(claude): 在 Claude OAuth 路径上转发客户端 `tool-search-tool-2025-10-19` anthropic-beta** — 启用延迟工具时,Claude Code 协商 `tool-搜索-tool-2025-10-19` beta,但 OmniRoute 在两个 Claude 代码路径上均将其丢弃,因此 claude.ai 后端以 `400 Tool reference not found` 拒绝每个延迟工具请求。新的白名单合并 (`mergeClientAnthropicBeta`) 现在在两个路径上将客户端协商的 beta 合并到出站集合中,仅追加白名单中的客户端 beta(保留 #3415 修复)。([#3999](https://github.com/diegosouzapw/OmniRoute/pull/3999) — 感谢 @huohua-dev / @diegosouzapw) +- **fix(executor): 在非流式请求中去除 `stream_options`(NVIDIA NIM 400)** — 无论 `流` 如何都发送 `stream_options: { include_usage: true }` 的客户端(如 OpenAI Python SDK)在非流式调用中将其原样传递,NVIDIA NIM 以 `400 "Stream options can only be defined when 流=True"` 拒绝。`DefaultExecutor.transformRequest` 现在在 `流` 为 false 时去除 `stream_options`;流式注入路径不变。([#4000](https://github.com/diegosouzapw/OmniRoute/pull/4000) — 感谢 @andrea-kingautomation / @daniij / @diegosouzapw) +- **fix(sse): 在 `getUnsupportedParams` (mimocode) 中保护无模型注册表条目** — 没有模型映射的注册表条目 (mimocode) 在计算不支持参数时抛出异常;查找现在保护无模型情况,使请求验证不再崩溃。([#4015](https://github.com/diegosouzapw/OmniRoute/pull/4015) — 感谢 @diegosouzapw) +- **fix(perplexity-web): 解析结构化 `diff_block` 流使答案不再为空** — Perplexity web 将其答案作为 RFC-6902 `diff_block` 补丁流式传输,OmniRoute 在 `PENDING` 阶段未应用这些补丁,因此响应返回为空;解析器现在应用补丁并在 `COMPLETED` 时物化文本。([#4001](https://github.com/diegosouzapw/OmniRoute/pull/4001) — 感谢 @artickc) +- **fix(default-executor): 遵循 OpenAI 格式服务商的自定义 `providerSpecificData.baseUrl`** — 配置了自定义 base URL 的 OpenAI 格式服务商在推理路径上被忽略;默认执行器现在遵循 `providerSpecificData.baseUrl`,使请求到达配置的端点。([#4002](https://github.com/diegosouzapw/OmniRoute/pull/4002) — 感谢 @artickc) +- **fix(live-ws): 将 LiveWS sidecar 事件桥接到控制台** — LiveWS sidecar 发出的事件未到达控制台;它们现在被桥接,使实时 websocket 活动可见。(sidecar 认证 Token 解析中的 cookie 认证回归也已修正。)([#4004](https://github.com/diegosouzapw/OmniRoute/pull/4004) — 感谢 @megamen32) +- **fix(qwen-web): cookie 验证误报 — 检查响应 body 中的用户对象** — Qwen web cookie 验证将有效 cookie 报告为无效;现在检查响应 body 中的 `user` 对象,而非仅依赖状态码。([#3958](https://github.com/diegosouzapw/OmniRoute/pull/3958) — 感谢 @thezukiru) +- **fix(vision-bridge): 强制 tokenrouter DeepSeek 模型使用桥接** — tokenrouter DeepSeek 模型现在强制通过 Vision Bridge,使图像输入得到正确处理。([#3946](https://github.com/diegosouzapw/OmniRoute/pull/3946) — 感谢 @WormAlien) +- **fix(api): 对 `/api/auth/login` 上格式错误的 JSON 返回 400(而非 500)** — 登录端点上的格式错误 JSON body 返回不透明的 500;现在返回正确的 400。([#4031](https://github.com/diegosouzapw/OmniRoute/pull/4031) — 感谢 @rdself) +- **fix(dashboard): Playground 对比标签页加载 + HTTP 方法守卫** — Playground 对比标签页加载失败;加载路径已修复并添加了 HTTP 方法守卫。([#4024](https://github.com/diegosouzapw/OmniRoute/pull/4024) — 感谢 @rdself) +- **fix(proxy): 在特性标志后控制控制平面代理直接回退(fail-closed)** — 当固定代理不可达时,控制平面操作的直接连接容灾现在受特性标志控制并 fail-closed,因此固定代理永远不会被静默绕过,除非显式允许。([#3963](https://github.com/diegosouzapw/OmniRoute/pull/3963) — 感谢 @rdself) +- **fix(db): 持久化备份保留天数** — 备份保留天数设置在重启后未持久化;现在被持久存储。([#3970](https://github.com/diegosouzapw/OmniRoute/pull/3970) — 感谢 @rdself) +- **fix(dashboard): 优化服务商配额卡片显示** — 服务商配额卡片布局经过优化,呈现更清晰的配额/用量��示。([#3969](https://github.com/diegosouzapw/OmniRoute/pull/3969) — 感谢 @rdself) +- **fix(dashboard): 优化压缩设置、存储标签和侧边栏分组** — 打磨压缩设置 UI、澄清存储标签、整理侧边栏分组。([#4033](https://github.com/diegosouzapw/OmniRoute/pull/4033) — 感谢 @rdself) -### 🔒 Security & Hardening +### 🔒 安全与加固 -- **fix(security): eliminate a polynomial ReDoS in the combo `` tag regex** — `comboAgentMiddleware`'s cache-tag pattern wrapped the tag in an unbounded newline run (`(?:\n|\r)*`), making `.test()` / `.replace()` run in O(n²) on inputs with many newlines (CodeQL `js/polynomial-redos`). The detection pattern now matches only the core `` and the global strip pattern bounds the surrounding newline runs, keeping it linear; detection / extraction / multi-tag stripping behavior is unchanged. ([#3982](https://github.com/diegosouzapw/OmniRoute/pull/3982) — thanks @diegosouzapw) -- **ci(security): harden workflows — artipacked `persist-credentials`, cache-poisoning, SC2086** — GitHub Actions workflows were hardened against the artipacked `persist-credentials` leak and cache-poisoning, and shell-quoting (`SC2086`) issues were fixed. ([#3965](https://github.com/diegosouzapw/OmniRoute/pull/3965) — thanks @diegosouzapw) -- **ci(quality): flip require-tighten + osv + Trivy to blocking (cycle-end)** — the per-module require-tighten check and the OSV / Trivy scanners moved from advisory to blocking for the v3.8.27 cycle close, so new dependency or coverage regressions fail CI. ([#3984](https://github.com/diegosouzapw/OmniRoute/pull/3984) — thanks @diegosouzapw) -- **chore(deps): dependabot security bumps + drop unused gray-matter** — applies a batch of Dependabot security bumps and removes the unused `gray-matter` dependency from the tree. ([#4036](https://github.com/diegosouzapw/OmniRoute/pull/4036) — thanks @diegosouzapw) -- **chore(deps): automated dependency bumps** — Dependabot upgraded the production dependency group (13 updates), `vite`, `form-data`, and the `npm_and_yarn` group. ([#3915](https://github.com/diegosouzapw/OmniRoute/pull/3915), [#3942](https://github.com/diegosouzapw/OmniRoute/pull/3942), [#3943](https://github.com/diegosouzapw/OmniRoute/pull/3943), [#3944](https://github.com/diegosouzapw/OmniRoute/pull/3944) — thanks @dependabot) +- **fix(security): 消除 combo `` 标签正则中的多项式 ReDoS** — `comboAgentMiddleware` 的缓存标签模式将标签包裹在无界换行运行中 (`(?:\n|\r)*`),使 `.test()` / `.replace()` 在大量换行的输入上以 O(n²) 运行(CodeQL `js/polynomial-redos`)。检测模式现在仅匹配核心 ``,全局剥离模式限制周围的换行运行,保持线性;检测/提取/多标签剥离行为不变。([#3982](https://github.com/diegosouzapw/OmniRoute/pull/3982) — 感谢 @diegosouzapw) +- **ci(security): 加固工作流 — artipacked `persist-凭证`、缓存投毒、SC2086** — GitHub Actions 工作流针对 artipacked `persist-凭证` 泄漏和缓存投毒进行了加固,并修复了 shell 引用 (`SC2086`) 问题。([#3965](https://github.com/diegosouzapw/OmniRoute/pull/3965) — 感谢 @diegosouzapw) +- **ci(quality): 将 require-tighten + OSV + Trivy 转为阻断(周期结束)** — 每个模块的 require-tighten 检查和 OSV / Trivy 扫描器从建议转为阻断,用于 v3.8.27 周期收尾,使新的依赖或覆盖率回退导致 CI 失败。([#3984](https://github.com/diegosouzapw/OmniRoute/pull/3984) — 感谢 @diegosouzapw) +- **chore(deps): dependabot 安全升级 + 移除未使用的 gray-matter** — 应用一批 Dependabot 安全升级,并从依赖树中移除未使用的 `gray-matter`。([#4036](https://github.com/diegosouzapw/OmniRoute/pull/4036) — 感谢 @diegosouzapw) +- **chore(deps): 自动依赖升级** — Dependabot 升级了生产依赖组(13 个更新)、`vite`、`form-data` 和 `npm_and_yarn` 组。([#3915](https://github.com/diegosouzapw/OmniRoute/pull/3915), [#3942](https://github.com/diegosouzapw/OmniRoute/pull/3942), [#3943](https://github.com/diegosouzapw/OmniRoute/pull/3943), [#3944](https://github.com/diegosouzapw/OmniRoute/pull/3944) — 感谢 @dependabot) -### 🧹 Internal / Quality / Docs +### 🧹 内部 / 质量 / 文档 -- **feat(ci): Quality Gate v2 — Onda 0 + Onda 1** — first two waves of the Quality Gate v2 program: gate flips, test-impact analysis (TIA), SAST, DAST-smoke, and mutation-testing infrastructure. ([#4016](https://github.com/diegosouzapw/OmniRoute/pull/4016) — thanks @diegosouzapw) -- **refactor: modularize the provider registry into individual provider plugins** — `providerRegistry.ts` was split into individual per-provider plugin modules (non-stacked). A forward-fix restored the `byteplus` + `mimocode` modules dropped by the move. ([#3993](https://github.com/diegosouzapw/OmniRoute/pull/3993) — thanks @oyi77 / @diegosouzapw) -- **refactor: modularize schemas (non-stacked)** — the request/response schema definitions were split into individual modules to reduce file size and improve maintainability. ([#3988](https://github.com/diegosouzapw/OmniRoute/pull/3988) — thanks @oyi77) -- **fix: restore unit regressions dropped by the lossy schema/registry modularizations** — the schema/registry modularizations (#3988, #3993) silently dropped internal logic covered by unit tests; this PR restores the regressed units. ([#4030](https://github.com/diegosouzapw/OmniRoute/pull/4030) — thanks @diegosouzapw) -- **refactor(dashboard): settings UI layout + API Keys naming** — the settings UI layout was reorganized and the "API Keys" naming clarified. ([#4020](https://github.com/diegosouzapw/OmniRoute/pull/4020) — thanks @rdself) -- **大量UI显示和i18n优化 (dashboard UI display + i18n improvements)** — a batch of dashboard UI-display refinements and i18n string improvements. ([#3973](https://github.com/diegosouzapw/OmniRoute/pull/3973) — thanks @rdself) -- **fix(ci): scope TIA to `node:test` unit files only** — test-impact analysis was matching files the `node:test` runner doesn't execute, producing 99 false failures; the TIA glob now mirrors the `test:unit` glob exactly. ([#4035](https://github.com/diegosouzapw/OmniRoute/pull/4035) — thanks @diegosouzapw) -- **fix(ci): electron-release publish-npm needs `contents: write`** — the reusable npm-publish job invoked by the electron release lacked `contents: write`, causing a v3.8.26 `startup_failure`; the permission was granted. ([#3966](https://github.com/diegosouzapw/OmniRoute/pull/3966) — thanks @diegosouzapw) -- **test(opencode-plugin): ESM default-export test (drop the stale CJS bundle test)** — replaces the stale CJS bundle test with an ESM default-export test, following up the #3883 ESM-only migration. ([#3967](https://github.com/diegosouzapw/OmniRoute/pull/3967) — thanks @diegosouzapw) -- **fix(ci): Fix promptfoo security-assertion parsing** — the promptfoo (DAST/security eval) assertion parser was corrected so security assertions are read reliably. ([#4032](https://github.com/diegosouzapw/OmniRoute/pull/4032) — thanks @rdself) -- **docs(troubleshooting): note that the MITM proxy cannot intercept Windows-host apps under WSL** — documents that the MITM proxy running inside WSL cannot intercept traffic from apps on the Windows host. ([#4003](https://github.com/diegosouzapw/OmniRoute/pull/4003) — thanks @diegosouzapw) -- **chore(quality): maintenance roll-up** — assorted quality-gate hygiene that does not change runtime behavior: re-baseline `validation.ts` for the #3958 qwen body-check, allowlist the `socks` dependency declared by #4004, ignore jscpd major bumps (the v5 Rust rewrite breaks the pinned duplication gate), untrack an accidentally-committed root `node_modules` symlink (and gitignore it), rehome the #3972 logs auto-refresh test so a runner collects it, and open the v3.8.27 development cycle. (thanks @diegosouzapw) +- **feat(ci): Quality Gate v2 — Onda 0 + Onda 1** — Quality Gate v2 计划的前两波:门控翻转、测试影响分析 (TIA)、SAST、DAST-smoke 和变异测试基础设施。([#4016](https://github.com/diegosouzapw/OmniRoute/pull/4016) — 感谢 @diegosouzapw) +- **refactor: 将服务商注册表模块化为独立的服务商插件** — `providerRegistry.ts` 拆分为独立的按服务商插件模块(非堆叠)。后续修复恢复了迁移中丢失的 `byteplus` + `mimocode` 模块。([#3993](https://github.com/diegosouzapw/OmniRoute/pull/3993) — 感谢 @oyi77 / @diegosouzapw) +- **refactor: 模块化 schemas(非堆叠)** — 请求/响应 schema 定义拆分为独立模块,减少文件大小并提高可维护性。([#3988](https://github.com/diegosouzapw/OmniRoute/pull/3988) — 感谢 @oyi77) +- **fix: 恢复因 schema/注册表模块化导致的单元测试回归** — schema/注册表模块化 (#3988, #3993) 静默丢弃了被单元测试覆盖的内部逻辑;本 PR 恢复了受影响的单元测试。([#4030](https://github.com/diegosouzapw/OmniRoute/pull/4030) — 感谢 @diegosouzapw) +- **refactor(dashboard): 设置 UI 布局 + API Keys 命名** — 设置 UI 布局已重组,"API Keys" 命名已明确。([#4020](https://github.com/diegosouzapw/OmniRoute/pull/4020) — 感谢 @rdself) +- **大量UI显示和i18n优化 (dashboard UI display + i18n improvements)** — 一批控制台 UI 显示优化和 i18n 字符串改进。([#3973](https://github.com/diegosouzapw/OmniRoute/pull/3973) — 感谢 @rdself) +- **fix(ci): 将 TIA 作用域限制为仅 `node:test` 单元文件** — 测试影响分析匹配了 `node:test` 运行器不执行的文件,产生 99 个误报失败;TIA glob 现在与 `test:unit` glob 完全一致。([#4035](https://github.com/diegosouzapw/OmniRoute/pull/4035) — 感谢 @diegosouzapw) +- **fix(ci): electron-release publish-npm needs `contents: write`** — the reusable npm-publish job invoked by the electron release lacked `contents: write`, causing a v3.8.26 `startup_failure`; the permission was granted. ([#3966](https://github.com/diegosouzapw/OmniRoute/pull/3966) — 感谢 @diegosouzapw) +- **test(opencode-plugin): ESM default-export test (drop the stale CJS bundle test)** — replaces the stale CJS bundle test with an ESM 默认-export test, following up the #3883 ESM-only 迁移. ([#3967](https://github.com/diegosouzapw/OmniRoute/pull/3967) — 感谢 @diegosouzapw) +- **fix(ci): Fix promptfoo security-assertion parsing** — the promptfoo (DAST/security eval) 断言 parser was corrected so security assertions are read reliably. ([#4032](https://github.com/diegosouzapw/OmniRoute/pull/4032) — 感谢 @rdself) +- **docs(troubleshooting): note that the MITM proxy cannot intercept Windows-host apps under WSL** — documents that the MITM 代理 running inside WSL cannot intercept traffic from apps on the Windows host. ([#4003](https://github.com/diegosouzapw/OmniRoute/pull/4003) — 感谢 @diegosouzapw) +- **chore(quality): maintenance roll-up** — assorted quality-gate hygiene that 不 change runtime behavior: re-baseline `validation.ts` for the #3958 qwen body-check, allowlist the `socks` dependency declared by #4004, ignore jscpd major bumps (the v5 Rust rewrite breaks the pinned duplication gate), untrack an accidentally-committed root `node_modules` symlink (and gitignore it), rehome the #3972 logs auto-refresh test so a runner collects it, and open the v3.8.27 development cycle. (thanks @diegosouzapw) --- ## [3.8.26] — 2026-06-15 -### ✨ New Features +### ✨ 新功能 -- **feat(media): Vertex AI (Google) speech, transcription, music & video generation** — Vertex AI's Google media models are now routable through dynamic discovery: speech synthesis, audio transcription, music generation, and video generation. ([#3929](https://github.com/diegosouzapw/OmniRoute/pull/3929) — thanks @artickc) -- **feat(glm): add GLM-5.2 with effort-tier routing (high/max)** — GLM-5.2 is registered with high/max effort-tier routing. ([#3885](https://github.com/diegosouzapw/OmniRoute/pull/3885) — thanks @dhaern) -- **feat(combo): add a sticky round-robin target limit** — round-robin combos can cap how many targets stay "sticky" within a session (`stickyRoundRobinLimit`), balancing stickiness against spread. ([#3846](https://github.com/diegosouzapw/OmniRoute/pull/3846) — thanks @adivekar-utexas) -- **feat(openrouter): connection presets** — OpenRouter connections support reusable presets (provider routing / sort / quantization preferences), selectable when adding a connection. ([#3878](https://github.com/diegosouzapw/OmniRoute/pull/3878) — thanks @rdself) +- **feat(media): Vertex AI (Google) 语音合成、转录、音乐和视频生成** — Vertex AI 的 Google 媒体模型现在可通过动态发现进行路由:语音合成、音频转录、音乐生成和视频生成。([#3929](https://github.com/diegosouzapw/OmniRoute/pull/3929) — 感谢 @artickc) +- **feat(glm): 添加 GLM-5.2 的 effort-tier 路由 (high/max)** — GLM-5.2 已注册 high/max effort-tier 路由。([#3885](https://github.com/diegosouzapw/OmniRoute/pull/3885) — 感谢 @dhaern) +- **feat(combo): 添加粘性轮询目标限制** — 轮询 combo 可限制会话内保持 "粘性" 的目标数量 (`stickyRoundRobinLimit`),在粘性和分散之间取得平衡。([#3846](https://github.com/diegosouzapw/OmniRoute/pull/3846) — 感谢 @adivekar-utexas) +- **feat(openrouter): 连接预设** — OpenRouter 连接支持可重用预设(服务商路由/排序/量化偏好),添加连接时可选择。([#3878](https://github.com/diegosouzapw/OmniRoute/pull/3878) — 感谢 @rdself) -### 🐛 Fixed +### 🐛 问题修复 -- **fix(compression/memory): stop memory + compression from poisoning the upstream prompt cache** — with compression and/or memory enabled, requests to caching providers (Anthropic-family) missed the prompt cache on every turn, multiplying cost. Two root causes: (1) memory injection prepended the retrieved memories — which **vary per user query** — at index 0 of the message array, shifting the entire cacheable prefix every turn; memory is now inserted just before the last user message when the request carries `cache_control` breakpoints, keeping the cacheable prefix (system prompt + prior turns) byte-stable. (2) the cache-aware `skipSystemPrompt` flag computed by `getCacheAwareStrategy()` was dropped by `selectCompressionStrategy()` (which can only return a mode), so the system prompt could still be compressed under caching; a new `resolveCacheAwareConfig()` now forces `preserveSystemPrompt` on for caching requests. ([#3936](https://github.com/diegosouzapw/OmniRoute/pull/3936), closes [#3890](https://github.com/diegosouzapw/OmniRoute/issues/3890) — thanks @xenstar / @diegosouzapw) -- **fix(providers): register BytePlus ModelArk so its API key can be added** — adding a BytePlus (`ark-…`) key reported "invalid". `byteplus` was present in the provider catalog (`APIKEY_PROVIDERS`) but **never registered in the routing registry**, so key validation fell through to `{ unsupported: true }` → HTTP 400 → the UI rendered every key as invalid (and the provider was unusable for inference). Added a registry entry modeled on the existing Volcengine Ark provider: OpenAI-compatible format, base `https://ark.ap-southeast.bytepluses.com/api/v3` (region `ap-southeast-1`), `Authorization: Bearer` auth, seeded with the catalog's advertised models (Seed 2.0, Kimi K2 Thinking, GLM 4.7, GPT-OSS-120B). ([#3935](https://github.com/diegosouzapw/OmniRoute/pull/3935), closes [#3877](https://github.com/diegosouzapw/OmniRoute/issues/3877) — thanks @nikohd12 / @diegosouzapw) -- **fix(providers): Nous Research key validation no longer fails on a stale probe model** — adding a valid Nous Research API key reported "invalid" even though the same key worked via the portal's copy-shell `curl`. The validation probe sent `model: "nousresearch/hermes-4-70b"`, which Nous does not serve, so the API returned `400` and the validator (which only treated `200`/`429` as success) reported the key invalid. The probe now uses the real `Hermes-4-70B` slug, and any non-auth 4xx (`400`/`404`/`422`) is treated as a valid key (the request shape was wrong, not the credentials) — mirroring the longcat/nvidia validators so a future model rename can't re-break key validation. ([#3934](https://github.com/diegosouzapw/OmniRoute/pull/3934), closes [#3881](https://github.com/diegosouzapw/OmniRoute/issues/3881) — thanks @FerLuisxd / @diegosouzapw) -- **fix(stream): persist mid-stream upstream failures** — when an upstream stream fails partway through, the partial response and incremental usage are now finalized and persisted instead of lost; extracts a shared `streamFailureFinalization` path and merges incremental Claude usage (follow-up to #3879). ([#3937](https://github.com/diegosouzapw/OmniRoute/pull/3937) — thanks @rdself) -- **fix(perplexity-web): update the request payload to schema v2.18 (HTTP 400)** — Perplexity web requests started returning HTTP 400; the request payload was updated to Perplexity's v2.18 schema. ([#3938](https://github.com/diegosouzapw/OmniRoute/pull/3938) — thanks @artickc) -- **fix(stream): keep the in-flight request payload in sync** — the pending-by-id request record is now updated in place (`Object.assign`) so the in-flight payload stays consistent with what was dispatched (coexists with #3937). ([#3940](https://github.com/diegosouzapw/OmniRoute/pull/3940) — thanks @rdself) -- **fix: stabilize reasoning streams and request logs** — reasoning-token streaming and the request-log capture path were stabilized to avoid dropped/duplicated reasoning frames and inconsistent log entries. ([#3879](https://github.com/diegosouzapw/OmniRoute/pull/3879) — thanks @rdself) -- **fix(opencode-plugin): include nested combo-refs in the LCD context window** — the OpenCode plugin now follows nested combo references when computing the least-common-denominator context window, so a combo nested inside another no longer reports an inflated window. ([#3910](https://github.com/diegosouzapw/OmniRoute/pull/3910) — thanks @herjarsa) -- **fix(models): correct the failed-model auto-hide defaults** — the defaults governing when a failed model is auto-hidden were corrected, and auto-hide is now opt-in so models are no longer dropped unexpectedly. ([#3930](https://github.com/diegosouzapw/OmniRoute/pull/3930) — thanks @rdself) -- **fix(openrouter): show the preset field when editing a connection** — the connection-preset field appeared only when creating a connection, not when editing one; it now appears in both (follow-up to #3878). ([#3921](https://github.com/diegosouzapw/OmniRoute/pull/3921) — thanks @rdself) -- **fix(sse): announce the assistant role on the first delta (Responses→Chat)** — the first SSE delta of a Responses-API→Chat-Completions stream now carries `role: "assistant"`, which strict OpenAI-compatible clients expect before content deltas. ([#3911](https://github.com/diegosouzapw/OmniRoute/pull/3911) — thanks @diego-anselmo) -- **fix(vertex): add the generative-language scope so SA-JSON model discovery works** — Vertex service-account (SA-JSON) model discovery failed without the `generative-language` OAuth scope; the scope is now requested. ([#3922](https://github.com/diegosouzapw/OmniRoute/pull/3922) — thanks @artickc) -- **fix(proxy): direct-connection fallback for control-plane ops when a pinned proxy is unreachable** — control-plane operations (validation, discovery) now fall back to a direct connection when a connection's pinned proxy is unreachable, instead of failing outright. ([#3906](https://github.com/diegosouzapw/OmniRoute/pull/3906) — thanks @zhiru) -- **fix(providers): prevent zombie-socket hangs for zai/glm and tighten the default keepAlive** — zai/glm could hang on dead keep-alive sockets; the default keepAlive was tightened to evict zombie sockets. ([#3907](https://github.com/diegosouzapw/OmniRoute/pull/3907) — thanks @insoln) -- **fix(setup): remove the stale CJS bundle check from setup-open-code** — the OpenCode setup helper no longer checks for a CJS bundle that the now ESM-only plugin no longer ships. ([#3908](https://github.com/diegosouzapw/OmniRoute/pull/3908) — thanks @herjarsa) -- **fix(opencode-plugin): drop the CJS bundle to fix the OpenCode plugin loader** — the plugin is now ESM-only, fixing the OpenCode loader which failed on the dual CJS/ESM build. ([#3883](https://github.com/diegosouzapw/OmniRoute/pull/3883) — thanks @herjarsa) -- **fix(mcp): fall back to `node:sqlite` when the better-sqlite3 binding is missing** — the MCP server now falls back to Node's built-in `node:sqlite` when the native better-sqlite3 binding is unavailable, instead of crashing. ([#3887](https://github.com/diegosouzapw/OmniRoute/pull/3887) — thanks @megamen32) -- **fix(models): correct the generate-models alias lookup** — alias resolution during model generation was corrected so aliased model ids resolve to their canonical entry. ([#3870](https://github.com/diegosouzapw/OmniRoute/pull/3870) — thanks @YunyunZhai) -- **fix(combo): guard the candidate pool against an empty array** — combo candidate-pool selection no longer throws when the pool resolves to an empty array. ([#3871](https://github.com/diegosouzapw/OmniRoute/pull/3871) — thanks @YunyunZhai) +- **fix(compression/memory): 停止记忆 + 压缩污染上游提示缓存** — 启用压缩和/或记忆时,对缓存服务商(Anthropic 系列)的请求在每个轮次上都错过提示缓存,导致成本倍增。两个根本原因:(1) 记忆注入将检索到的记忆(**因用户查询而异**)前置到消息数组的索引 0,使整个可缓存前缀在每个轮次上发生偏移;记忆现在在请求携带 `cache_control` 断点时,插入到最后一条用户消息之前,保持可缓存前缀(系统提示 + 先前轮次)字节稳定。(2) 由 `getCacheAwareStrategy()` 计算的缓存感知 `skipSystemPrompt` 标志被 `selectCompressionStrategy()` 丢弃(该函数只能返回模式),因此系统提示在缓存下仍可能被压缩;新的 `resolveCacheAwareConfig()` 现在强制为缓存请求开启 `preserveSystemPrompt`。([#3936](https://github.com/diegosouzapw/OmniRoute/pull/3936),关闭 [#3890](https://github.com/diegosouzapw/OmniRoute/issues/3890) — 感谢 @xenstar / @diegosouzapw) +- **fix(providers): 注册 BytePlus ModelArk 使 API key 可添加** — 添加 BytePlus (`ark-…`) key 时报 "invalid"。`byteplus` 存在于服务商目录 (`APIKEY_PROVIDERS`) 中但**从未在路由注册表中注册**,因此 key 验证回退到 `{ unsupported: true }` → HTTP 400 → UI 将所有 key 渲染为无效(且该服务商无法用于推理)。新增注册表条目,仿照现有 Volcengine Ark 服务商:OpenAI 兼容格式、基础 URL `https://ark.ap-southeast.bytepluses.com/api/v3`(区域 `ap-southeast-1`)、`Authorization: Bearer` 认证,使用目录中公告的模型(Seed 2.0、Kimi K2 Thinking、GLM 4.7、GPT-OSS-120B)作为种子。([#3935](https://github.com/diegosouzapw/OmniRoute/pull/3935),关闭 [#3877](https://github.com/diegosouzapw/OmniRoute/issues/3877) — 感谢 @nikohd12 / @diegosouzapw) +- **fix(providers): Nous Research key validation no longer fails on a stale probe model** — adding a valid Nous Research API Key reported "invalid" even though the same key worked via the portal's copy-shell `curl`. The validation probe sent `模型: "nousresearch/hermes-4-70b"`, which Nous 不 serve, so the API returned `400` and the validator (which only treated `200`/`429` as success) reported the key invalid. The probe now uses the real `Hermes-4-70B` slug, and any non-认证 4xx (`400`/`404`/`422`) is treated as a valid key (the 请求 shape was wrong, not the 凭证) — mirroring the longcat/nvidia validators so a future 模型 rename can't re-break key validation. ([#3934](https://github.com/diegosouzapw/OmniRoute/pull/3934), closes [#3881](https://github.com/diegosouzapw/OmniRoute/issues/3881) — 感谢 @FerLuisxd / @diegosouzapw) +- **fix(stream): 持久化流中途的上游失败** — 当上游流中途失败时,部分响应和增量用量现在被完成并持久化,而非丢失;提取了共享的 `streamFailureFinalization` 路径,并合并了增量 Claude 用量(#3879 的跟进)。([#3937](https://github.com/diegosouzapw/OmniRoute/pull/3937) — 感谢 @rdself) +- **fix(perplexity-web): 将请求载荷更新为 schema v2.18(HTTP 400)** — Perplexity web 请求开始返回 HTTP 400;请求载荷已更新为 Perplexity v2.18 schema。([#3938](https://github.com/diegosouzapw/OmniRoute/pull/3938) — 感谢 @artickc) +- **fix(stream): 保持进行中请求载荷同步** — 按 ID 挂起的请求记录现在原地更新 (`Object.assign`),使进行中载荷与已派发的内容保持一致(与 #3937 共存)。([#3940](https://github.com/diegosouzapw/OmniRoute/pull/3940) — 感谢 @rdself) +- **fix: 稳定推理流和请求日志** — 推理 token 流式传输和请求日志捕获路径已稳定,避免丢弃/重复推理帧和不一致的日志条目。([#3879](https://github.com/diegosouzapw/OmniRoute/pull/3879) — 感谢 @rdself) +- **fix(opencode-plugin): 在 LCD 上下文窗口中包含嵌套 Combo 引用** — OpenCode 插件现在在计算最小公分母上下文窗口时跟随嵌套的 Combo 引用,使嵌套在其他 Combo 中的 Combo 不再报告膨胀的窗口。([#3910](https://github.com/diegosouzapw/OmniRoute/pull/3910) — 感谢 @herjarsa) +- **fix(models): 修正失败模型自动隐藏的默认值** — 控制失败模型何时自动隐藏的默认值已修正,自动隐藏现在为主动选择加入,使模型不再意外被隐藏。([#3930](https://github.com/diegosouzapw/OmniRoute/pull/3930) — 感谢 @rdself) +- **fix(openrouter): 编辑连接时显示预设字段** — 连接预设字段仅在创建连接时出现,编辑时不显示;现在两者均显示(#3878 的跟进)。([#3921](https://github.com/diegosouzapw/OmniRoute/pull/3921) — 感谢 @rdself) +- **fix(sse): 在首个 delta 上声明助手角色(Responses→Chat)** — Responses-API→Chat-Completions 流的首个 SSE delta 现在携带 `role: "assistant"`,严格的 OpenAI 兼容客户端在内容 delta 之前需要该字段。([#3911](https://github.com/diegosouzapw/OmniRoute/pull/3911) — 感谢 @diego-anselmo) +- **fix(vertex): 添加 generative-language 作用域使 SA-JSON 模型发现正常工作** — Vertex 服务账户 (SA-JSON) 模型发现在缺少 `generative-language` OAuth 作用域时失败;该作用域现在被请求。([#3922](https://github.com/diegosouzapw/OmniRoute/pull/3922) — 感谢 @artickc) +- **fix(proxy): 固定代理不可达时控制平面操作回退到直接连接** — 控制平面操作(验证、发现)现在在连接固定代理不可达时容灾到直接连接,而非直接失败。([#3906](https://github.com/diegosouzapw/OmniRoute/pull/3906) — 感谢 @zhiru) +- **fix(providers): 防止 zai/glm 僵尸 socket 挂起并收紧默认 keepAlive** — zai/glm 可能在失效的 keep-alive socket 上挂起;默认 keepAlive 已收紧以驱逐僵尸 socket。([#3907](https://github.com/diegosouzapw/OmniRoute/pull/3907) — 感谢 @insoln) +- **fix(setup): 从 setup-open-code 中移除过时的 CJS bundle 检查** — OpenCode 设置助手不再检查 CJS bundle,因为现在仅 ESM 的插件已不再包含它。([#3908](https://github.com/diegosouzapw/OmniRoute/pull/3908) — 感谢 @herjarsa) +- **fix(opencode-plugin): 移除 CJS bundle 以修复 OpenCode 插件加载器** — 插件现在仅 ESM,修复了因双 CJS/ESM 构建而失败的 OpenCode 加载器。([#3883](https://github.com/diegosouzapw/OmniRoute/pull/3883) — 感谢 @herjarsa) +- **fix(mcp): better-sqlite3 绑定缺失时回退到 `node:sqlite`** — MCP 服务器现在在原生的 better-sqlite3 绑定不可用时回退到 Node 内置的 `node:sqlite`,而非崩溃。([#3887](https://github.com/diegosouzapw/OmniRoute/pull/3887) — 感谢 @megamen32) +- **fix(models): 修正 generate-models 别名查找** — 模型生成期间的别名解析已修正,使别名模型 ID 能解析到其规范条目。([#3870](https://github.com/diegosouzapw/OmniRoute/pull/3870) — 感谢 @YunyunZhai) +- **fix(combo): 保护候选池防止空数组** — Combo 候选池选择在池解析为空数组时不再抛出异常。([#3871](https://github.com/diegosouzapw/OmniRoute/pull/3871) — 感谢 @YunyunZhai) -### 🔒 Security & Hardening +### 🔒 安全与加固 -- **fix(security): bump form-data + vite (2 HIGH), harden workflow template-injection & allowlist guarded `workflow_run`** — two HIGH Dependabot advisories (`form-data`, `vite`) were upgraded; GitHub Actions workflows were hardened against `${{ }}` template-injection (untrusted values now passed via `env:`); and the guarded `workflow_run` trigger was allowlisted. ([#3949](https://github.com/diegosouzapw/OmniRoute/pull/3949) — thanks @diegosouzapw) +- **fix(security): 升级 form-data + vite(2 个 HIGH),加固工作流模板注入并白名单守卫 `workflow_run`** — 两个 HIGH Dependabot 建议(`表单-data`、`vite`)已升级;GitHub Actions 工作流针对 `${{ }}` 模板注入进行了加固(不受信任的值现在通过 `env:` 传递);受保护的 `workflow_run` 触发器已加入白名单。([#3949](https://github.com/diegosouzapw/OmniRoute/pull/3949) — 感谢 @diegosouzapw) -### 🧹 Internal / Quality / Docs +### 🧹 内部 / 质量 / 文档 -- **fix(ci): grant `contents: write` to the npm publish job for SBOM attach** — the v3.8.25 TokenPermissions hardening set the npm-publish `publish` job to `contents: read`, but its "Attach SBOM to GitHub Release" step (`gh release upload`) needs `contents: write` and failed with HTTP 403 on the v3.8.25 release (npm / GitHub Packages / opencode-plugin / Docker / Electron all published fine; only the SBOM attach broke — the v3.8.25 SBOM was attached manually). ([#3874](https://github.com/diegosouzapw/OmniRoute/pull/3874) — thanks @diegosouzapw) -- **ci(quality): make zizmor / gitleaks / osv scanners functional + freeze advisory baselines** — the supply-chain scanners are now actually executed (correct install + invocation) with frozen advisory baselines so new findings surface as diffs. ([#3947](https://github.com/diegosouzapw/OmniRoute/pull/3947) — thanks @diegosouzapw) -- **ci(quality): fix scanner install + size-limit preset, promote `codeqlAlerts` to blocking** — corrected the scanner install and the size-limit preset, and promoted the `codeqlAlerts` ratchet from advisory to blocking. ([#3945](https://github.com/diegosouzapw/OmniRoute/pull/3945) — thanks @diegosouzapw) -- **ci(quality): wire Stryker mutation testing as an advisory nightly** — Stryker mutation testing runs nightly (advisory) — Quality Gates Fase 7 · Task 11. ([#3898](https://github.com/diegosouzapw/OmniRoute/pull/3898) — thanks @diegosouzapw) -- **ci(quality): freeze per-module coverage floors + wire require-tighten (advisory)** — per-module coverage floors are frozen with an advisory "require-tighten" check that flags modules drifting below their floor. ([#3901](https://github.com/diegosouzapw/OmniRoute/pull/3901) — thanks @diegosouzapw) -- **ci(quality): enforce the stale-allowlist check on `check-known-symbols`** — stale allowlist entries (suppressing a symbol that no longer exists) now fail the gate — Fase 6A.3 follow-up. ([#3899](https://github.com/diegosouzapw/OmniRoute/pull/3899) — thanks @diegosouzapw) -- **test(ci): de-flake pipeline-payloads via per-test re-seed + honest reset** — the pipeline-payloads suite now re-seeds per test and performs an honest cache reset, eliminating a cross-test ordering flake. ([#3893](https://github.com/diegosouzapw/OmniRoute/pull/3893) — thanks @diegosouzapw) -- **fix(ci): drop the `secrets`-in-job-`if` from nightly-llm-security** — referencing `secrets` in a job-level `if` caused a `startup_failure` on push; the gating was moved so the workflow starts cleanly. ([#3892](https://github.com/diegosouzapw/OmniRoute/pull/3892) — thanks @diegosouzapw) -- **test: reconcile the runtime-timeouts keepAlive baseline to 4000 after the #3907 source revert** — the keepAlive assertion was realigned to the source value (4000) after #3907's source-side revert. ([#3933](https://github.com/diegosouzapw/OmniRoute/pull/3933) — thanks @diegosouzapw) -- **chore(repo): nest quality-gate state under `config/quality`, declutter the repo root** — baselines / allowlists / metrics moved under `config/quality/`, trimming the tracked root file count. ([#3896](https://github.com/diegosouzapw/OmniRoute/pull/3896) — thanks @diegosouzapw) -- **docs: refresh the provider count to 226 + regenerate `PROVIDER_REFERENCE.md`** — the README advertised a stale `177 providers`; the canonical generator (`scripts/docs/gen-provider-reference.ts`) now reports **226 unique provider IDs**, so the README badges/anchors and the generated provider reference were brought in sync. Also adds a documentation audit/sync report. (thanks @diegosouzapw) -- **docs: sync all documentation to v3.8.24 + count-guard & wiki/prose CI** — a full documentation sync with a strict provider/locale count-guard plus Vale / markdownlint prose CI. ([#3804](https://github.com/diegosouzapw/OmniRoute/pull/3804) — thanks @diegosouzapw) -- **docs: regenerate stale counts to canonical values** — 226 providers / 87 MCP tools / 15 strategies / 42 locales. ([#3904](https://github.com/diegosouzapw/OmniRoute/pull/3904) — thanks @diegosouzapw) -- **docs(quality): correct the stale gate count + add an opt-in agent-lsp scaffold** — ([#3902](https://github.com/diegosouzapw/OmniRoute/pull/3902) — thanks @diegosouzapw) -- **docs(mcp): correct the MCP tool-inventory diagram source + text to 87 tools** — ([#3909](https://github.com/diegosouzapw/OmniRoute/pull/3909) — thanks @diegosouzapw) -- **docs: update the compression section to the 9-engine multi-layer stack** — ([#3894](https://github.com/diegosouzapw/OmniRoute/pull/3894) — thanks @diegosouzapw) -- **ci(docs): automate GitHub wiki sync (add missing pages + cover counts)** — ([#3900](https://github.com/diegosouzapw/OmniRoute/pull/3900) — thanks @diegosouzapw) -- **docs: require a dedicated git worktree + branch per development task (Hard Rule #19)** — codifies the worktree-isolation rule after the shared-checkout incidents. ([#3939](https://github.com/diegosouzapw/OmniRoute/pull/3939) — thanks @diegosouzapw) -- **fix(docs): add MDX frontmatter to `DOCUMENTATION_AUDIT_REPORT` so the fumadocs build passes** — the audit report lacked the `title:` frontmatter MDX pages require. (thanks @diegosouzapw) +- **fix(ci): 为 npm 发布任务授予 `contents: write` 权限以附加 SBOM** — v3.8.25 TokenPermissions 加固将 npm-publish `publish` 任务设为 `contents: read`,但其 "Attach SBOM to GitHub Release" 步骤 (`gh release upload`) 需要 `contents: write`,在 v3.8.25 发布中因 HTTP 403 失败(npm / GitHub Packages / opencode-插件 / Docker / Electron 均正常发布;仅 SBOM 附加失败 — v3.8.25 SBOM 已手动附加)。([#3874](https://github.com/diegosouzapw/OmniRoute/pull/3874) — 感谢 @diegosouzapw) +- **ci(quality): 使 zizmor / gitleaks / OSV 扫描器功能可用 + 冻结建议基线** — 供应链扫描器现在实际执行(正确的安装 + 调用),并冻结建议基线,使新发现作为差异呈现。([#3947](https://github.com/diegosouzapw/OmniRoute/pull/3947) — 感谢 @diegosouzapw) +- **ci(quality): 修复扫描器安装 + size-limit 预设,将 `codeqlAlerts` 从建议提升为阻断** — 修正了扫描器安装和 size-limit 预设,将 `codeqlAlerts` 从建议提升为阻断。([#3945](https://github.com/diegosouzapw/OmniRoute/pull/3945) — 感谢 @diegosouzapw) +- **ci(quality): 接入 Stryker 变异测试(建议,每夜运行)** — Stryker 变异测试每夜运行(建议)— Quality Gates Fase 7 · Task 11。([#3898](https://github.com/diegosouzapw/OmniRoute/pull/3898) — 感谢 @diegosouzapw) +- **ci(quality): 冻结每个模块的覆盖率底线 + 接入 require-tighten(建议)** — 每个模块的覆盖率底线已冻结,建议的 "require-tighten" 检查标记低于底线的模块。([#3901](https://github.com/diegosouzapw/OmniRoute/pull/3901) — 感谢 @diegosouzapw) +- **ci(quality): 在 `check-known-symbols` 上强制执行过期白名单检查** — 过期的白名单条目(抑制不再存在的符号)现在使门控失败 — Fase 6A.3 跟进。([#3899](https://github.com/diegosouzapw/OmniRoute/pull/3899) — 感谢 @diegosouzapw) +- **test(ci): 通过每次测试重新播种 + 真实重置来消除管线载荷测试的不稳定性** — 管线载荷测试套件现在每次测试重新播种并执行真实的缓存重置,消除了跨测试顺序的不稳定性。([#3893](https://github.com/diegosouzapw/OmniRoute/pull/3893) — 感谢 @diegosouzapw) +- **fix(ci): 从 nightly-llm-security 中移除任务级 `if` 中的 `secrets` 引用** — 在任务级 `if` 中引用 `secrets` 导致推送时出现 `startup_failure`;门控已移动使工作流正常启动。([#3892](https://github.com/diegosouzapw/OmniRoute/pull/3892) — 感谢 @diegosouzapw) +- **test: 在 #3907 源端回退后将 runtime-timeouts keepAlive 基线调整为 4000** — keepAlive 断言在 #3907 源端回退后已与源值 (4000) 重新对齐。([#3933](https://github.com/diegosouzapw/OmniRoute/pull/3933) — 感谢 @diegosouzapw) +- **chore(repo): 将质量门控状态嵌套到 `config/quality` 下,清理仓库根目录** — 基线/白名单/指标移至 `配置/quality/` 下,减少根目录跟踪文件数量。([#3896](https://github.com/diegosouzapw/OmniRoute/pull/3896) — 感谢 @diegosouzapw) +- **docs: 将服务商计数刷新为 226 + 重新生成 `PROVIDER_REFERENCE.md`** — README 中声明了过时的 `177 服务商`;规范生成器 (`scripts/docs/gen-服务商-reference.ts`) 现在报告 **226 个唯一服务商 ID**,因此 README 徽章/锚点和生成的服务商参考已同步。同时添加了文档审计/同步报告。(感谢 @diegosouzapw) +- **docs: 将所有文档同步到 v3.8.24 + 计数守卫和 wiki/prose CI** — 完整文档同步,包含严格的服务商/语言包计数守卫以及 Vale / markdownlint prose CI。([#3804](https://github.com/diegosouzapw/OmniRoute/pull/3804) — 感谢 @diegosouzapw) +- **docs: 重新生成过时的计数为规范值** — 226 个服务商 / 87 个 MCP 工具 / 15 个策略 / 42 个语言。([#3904](https://github.com/diegosouzapw/OmniRoute/pull/3904) — 感谢 @diegosouzapw) +- **docs(quality): 修正过时的门控计数 + 添加可选的 agent-lsp 脚手架** — ([#3902](https://github.com/diegosouzapw/OmniRoute/pull/3902) — 感谢 @diegosouzapw) +- **docs(mcp): 修正 MCP 工具清单图表源 + 文本为 87 个工具** — ([#3909](https://github.com/diegosouzapw/OmniRoute/pull/3909) — 感谢 @diegosouzapw) +- **docs: 更新压缩章节为 9 引擎多层堆叠** — ([#3894](https://github.com/diegosouzapw/OmniRoute/pull/3894) — 感谢 @diegosouzapw) +- **ci(docs): 自动化 GitHub wiki 同步(添加缺失页面 + 覆盖计数)** — ([#3900](https://github.com/diegosouzapw/OmniRoute/pull/3900) — 感谢 @diegosouzapw) +- **docs: 要求每个开发任务使用独立的 git worktree + 分支(硬规则 #19)** — 在共享检出事故后,编撰了 worktree 隔离规则。([#3939](https://github.com/diegosouzapw/OmniRoute/pull/3939) — 感谢 @diegosouzapw) +- **fix(docs): 添加 MDX frontmatter 到 `DOCUMENTATION_AUDIT_REPORT` 使 fumadocs 构建通过** — 审计报告缺少 MDX 页面所需的 `title:` frontmatter。(感谢 @diegosouzapw) --- ## [3.8.25] — 2026-06-14 -### ✨ New Features +### ✨ 新功能 -- **feat(compression): pluggable compression engines + async pipeline + Compression Studios** — a new prompt-compression subsystem with selectable engines (Lite / Aggressive / Ultra), an asynchronous compression pipeline wired into the chat core, and "Compression Studios" tooling for inspecting and tuning compression. ([#3848](https://github.com/diegosouzapw/OmniRoute/pull/3848)) -- **feat(compression-ui): unified compression configuration UI** — a Compression Hub with per-engine pages (Lite / Aggressive / Ultra), a combos editor, a dedicated sidebar entry, and live-WS default-on. ([#3860](https://github.com/diegosouzapw/OmniRoute/pull/3860)) -- **feat(security): prompt-injection guard across every LLM route + red-team suite** — the prompt-injection guard now runs on all LLM routes (chat, responses, embeddings, images, audio, rerank, search, moderations, videos, music) with a shared input sanitizer and a promptfoo-based red-team suite (Quality Gates Fase 8 · Bloco D). ([#3857](https://github.com/diegosouzapw/OmniRoute/pull/3857)) -- **feat(kiro): live per-account model discovery** — Kiro now discovers each account/tier's entitled models via CodeWhisperer `ListAvailableModels` (region-matched, with a static-catalog fallback). ([#3836](https://github.com/diegosouzapw/OmniRoute/pull/3836) — thanks @artickc) -- **feat(gemini/vertex): surface Veo video models in dynamic discovery** — Veo video models (`predictLongRunning`) now appear in Gemini/Vertex dynamic model discovery. ([#3839](https://github.com/diegosouzapw/OmniRoute/pull/3839) — thanks @artickc) -- **feat(mimocode): per-account proxy for multi-account round-robin** — each mimocode account can route through its own proxy (resolved per account by fingerprint via `runWithProxyContext`), with a "Distribute proxies" UI helper. ([#3837](https://github.com/diegosouzapw/OmniRoute/pull/3837) — thanks @pizzav-xyz) -- **feat(intelligence): expose Arena ELO sync as a feature flag** — the LM Arena ELO leaderboard sync is now toggleable (`ARENA_ELO_SYNC_ENABLED`, DB-override + env fallback). ([#3821](https://github.com/diegosouzapw/OmniRoute/pull/3821) — thanks @rdself) +- **feat(compression): 可插拔压缩引擎 + 异步管线 + Compression Studios** — 一个全新的提示-压缩子系统,支持可选择的引擎(Lite / Aggressive / Ultra)、接入聊天核心的异步压缩管线,以及用于检查和调优压缩的 "Compression Studios" 工具。 ([#3848](https://github.com/diegosouzapw/OmniRoute/pull/3848)) +- **feat(compression-ui): 统一压缩配置界面** — 新增 Compression Hub,包含每个引擎的子页面(Lite / Aggressive / Ultra)、组合编辑器、专用侧边栏入口以及默认开启的实时 WebSocket。 ([#3860](https://github.com/diegosouzapw/OmniRoute/pull/3860)) +- **feat(security): 覆盖所有 LLM 路由的提示注入守卫 + 红队测试套件** — 提示注入守卫现已覆盖所有 LLM 路由(chat、responses、embeddings、images、audio、rerank、搜索、moderations、videos、music),配备共享输入清洗器以及基于 promptfoo 的红队测试套件(Quality Gates Fase 8 · Bloco D)。 ([#3857](https://github.com/diegosouzapw/OmniRoute/pull/3857)) +- **feat(kiro): 每个账户的实时模型发现** — Kiro 现通过 CodeWhisperer `ListAvailableModels`(按区域匹配,带静态目录容灾)发现每个账户/层级的授权模型。 ([#3836](https://github.com/diegosouzapw/OmniRoute/pull/3836) — 感谢 @artickc) +- **feat(gemini/vertex): 在动态发现中展示 Veo 视频模型** — Veo 视频模型(`predictLongRunning`)现出现在 Gemini/Vertex 动态模型发现中。 ([#3839](https://github.com/diegosouzapw/OmniRoute/pull/3839) — 感谢 @artickc) +- **feat(mimocode): 多账户轮询的每账户代理** — 每个 mimocode 账户可通过其专属代理进行路由(按账户指纹通过 `runWithProxyContext` 解析),并配备 "分发代理" 界面辅助工具。 ([#3837](https://github.com/diegosouzapw/OmniRoute/pull/3837) — 感谢 @pizzav-xyz) +- **feat(intelligence): 将 Arena ELO 同步作为功能开关暴露** — LM Arena ELO 排行榜同步现已可切换(`ARENA_ELO_SYNC_ENABLED`,DB 覆盖 + 环境变量容灾)。 ([#3821](https://github.com/diegosouzapw/OmniRoute/pull/3821) — 感谢 @rdself) -### 🐛 Fixed +### 🐛 问题修复 -- **test(oauth): prove refresh_token preservation for the real gemini-cli / antigravity dispatch** — the #3679/#3766 regression test used a synthetic provider that routes through the generic `tokenUrl` path, so the fix was never proven for the actual Google-family providers, which dispatch through `refreshGoogleToken()` against the hardcoded `OAUTH_ENDPOINTS.google.token`. Added a test that drives `checkConnection` through the real `gemini-cli`/`antigravity` path (redirecting the Google token endpoint to a local server returning `invalid_grant`) and asserts the `refresh_token` is preserved (not nulled) — confirming these connections are not spuriously destroyed on a failed refresh. ([#3850](https://github.com/diegosouzapw/OmniRoute/issues/3850) — thanks @3xa228148) -- **fix(oauth): clear setup message for GitLab Duo instead of "Internal server error"** — adding a GitLab Duo connection without a registered OAuth client returned an opaque `Internal server error` at the Add Connection step. `buildAuthUrl` **threw** when `GITLAB_DUO_OAUTH_CLIENT_ID` was missing, and the route swallowed it into a generic 500. It now returns `null` (mirroring the Qoder provider) and the authorize route surfaces an actionable message: register an OAuth app at `https://gitlab.com/-/profile/applications` with redirect URI `http://localhost:20128/callback` and scopes `ai_features read_user`, then set `GITLAB_DUO_OAUTH_CLIENT_ID`. ([#3861](https://github.com/diegosouzapw/OmniRoute/issues/3861) — thanks @sidinsearch) -- **fix(db): persist the "Keep latest backups" retention setting** — changing the backup-retention count in Settings → Database backup retention had no effect: it always snapped back to 20 on refresh (and editing `.env` post-start was ignored too, since `process.env` isn't reloaded). `getDbBackupMaxFiles()` only read the `DB_BACKUP_MAX_FILES` env var — there was no setter and no persisted value. The value now round-trips through a dedicated `key_value` store (`getDbBackupMaxFiles` precedence: env override → persisted UI value → default 20), and the "Clean old backups" action persists the chosen count. Existing installs keep the historical default of 20 until explicitly changed. ([#3834](https://github.com/diegosouzapw/OmniRoute/issues/3834) — thanks @netstratego) -- **fix(sse): clamp Gemini thinking budget to the model's real cap (`reasoning_effort`/`effort=high` 400)** — translating OpenAI `reasoning_effort=high` (and Claude-Code `output_config.effort=high`) to a Gemini target sent a hardcoded `thinkingBudget: 32768`, which exceeds Flash-tier Gemini's real max of 24576 → upstream HTTP 400 (the `thinkingLevel=high` path already used 24576 and worked on the same model). `gemini-2.5-flash` now declares its real `thinkingBudgetCap` (24576) so the existing `capThinkingBudget()` chokepoint actually clamps, and the Claude→Gemini `output_config.effort` path — which previously sent the raw value with no cap at all — now routes through the same clamp (pro-tier, real cap 32768, is left untouched). ([#3842](https://github.com/diegosouzapw/OmniRoute/issues/3842) — thanks @andrea-kingautomation) -- **fix(intelligence): run pricing + models.dev sync from the live startup path** — like the Arena ELO sync (v3.8.24), the external **pricing sync** (`PRICING_SYNC_ENABLED`) and the **models.dev capability sync** (Settings → AI toggle) were only initialized from `server-init.ts`, which the Next standalone runtime never executes — and models.dev had no caller at all. Their toggles were inert in production. Both are now initialized from `instrumentation-node.ts` (self-gated, opt-in preserved, non-blocking, never fatal). (thanks @diegosouzapw) -- **test(proxy): guard the per-connection 'direct' bypass over a global proxy + clearer label** — the per-connection "Proxy Off" toggle (`proxyEnabled: false`) already overrides a configured **global** proxy (`resolveProxyForConnection` short-circuits to `level: "direct"` before the global step). Added an explicit regression test proving the bypass beats a global assignment (and round-trips on re-enable), and relabeled the UI to "Direct (bypass proxy)" so operators recognize it. Closes the verification gap in [#2996](https://github.com/diegosouzapw/OmniRoute/issues/2996). (thanks @diegosouzapw) -- **feat(connections): per-connection "disable cooldown" opt-out** — a connection can now opt out of the transient cooldown (`providerSpecificData.disableCooling`, with a toggle in the Edit Connection modal). When set, a recoverable failure still records the error/backoff but does **not** take the connection out of rotation, so it stays eligible for selection — useful for a primary key you never want parked on a blip. Terminal states (banned / expired / credits_exhausted) still apply. ([#2997](https://github.com/diegosouzapw/OmniRoute/issues/2997) — thanks @diegosouzapw) -- **fix(combo): restore sessionless combo stickiness + reasoning-aware readiness (504 / TPS regression after v3.8.14)** — #3399 (v3.8.16) replaced the ``-tag combo pinning with a server-side context-cache pin gated on a client `sessionId`. Clients that send no session id (most OpenAI-compatible tools) lost combo stickiness, so combos re-ran strategy selection every turn → upstream prompt-cache misses → cold high-reasoning starts (~78s) → intermittent `[504] Upstream request did not return response headers` + TPS collapse (only on combos). The pin now falls back to a stable per-conversation fingerprint (`extractSessionAffinityKey(body)`) when no session id is present — **only when `context_cache_protection` is on**, so #3399's anti-leak behaviour is preserved. Separately, the stream-readiness window now grants the +30s reasoning budget **unconditionally** for high-reasoning Codex GPT-5.x (small high-reasoning prompts were 504-ing at the 80s base regardless of stickiness). ([#3825](https://github.com/diegosouzapw/OmniRoute/issues/3825) — thanks @bypanghu) -- **test(combo): cover the `skipProviderBreaker` consumer gate** — the producer was tested but the consumer (whether a failed combo target trips the whole-provider circuit breaker) was not; the breaker decision is now an exported pure predicate (`shouldRecordProviderBreakerFailure`, behaviour-identical) with direct tests asserting a `connection_cooldown` 503 does not trip the breaker while a plain 503 does. Closes another deferred test gap from [#2743](https://github.com/diegosouzapw/OmniRoute/issues/2743). (thanks @diegosouzapw) -- **fix(providers): surface the real Devin error + correct the Windsurf auth instructions** — Devin chat returned a generic 502 "Invalid SSE response for non-streaming request" that swallowed the real cause (e.g. "Devin CLI not found"): an error-only SSE chunk (no `choices`) is now propagated with its sanitized message. The Windsurf "Visit windsurf.com/show-auth-token" instruction (the bare URL shows no token without an IDE-supplied `?state=`) now directs users to the `Windsurf: Provide Auth Token` command-palette flow. ([#3324](https://github.com/diegosouzapw/OmniRoute/issues/3324) — thanks @mikmaneggahommie) -- **fix(grok-web): clearer 403 message for anti-bot / IP-reputation blocks** — a Grok Web subscription validating from a flagged datacenter/VPS IP got a 403 that read like an invalid cookie, sending users to chase a cookie that was actually fine. A non-auth 403 (Cloudflare challenge / anti-bot body) now returns a message stating the cookie is likely OK and the block is IP-reputation-based — retry from a residential IP or configure a proxy (auth-shaped 403s keep the re-paste guidance). ([#3474](https://github.com/diegosouzapw/OmniRoute/issues/3474) — thanks @friedtofu1608) -- **fix(db): make the mass-pending-migrations safety threshold env-overridable** — restoring a backup DB from an older version could trip "Detected N pending migrations … threshold is 50" with no way to override the hardcoded `50`. The threshold is now configurable via `OMNIROUTE_MAX_PENDING_MIGRATIONS` (resolved at startup; `0` disables the check). ([#3416](https://github.com/diegosouzapw/OmniRoute/issues/3416) — thanks @samuraiIT) -- **test(proxy): cover the Vercel-relay `proxyFetch` path** — net-new tests for `buildVercelRelayHeaders` and the `vercel`-type relay short-circuit (`x-relay-target`/`-path`/`-auth`, TCP-skip, missing-auth fail-closed), closing one of the deferred test gaps tracked in [#2743](https://github.com/diegosouzapw/OmniRoute/issues/2743). (thanks @diegosouzapw) -- **fix(cli): surface `omniroute runtime repair` in the native-module error messages** — after a Node major upgrade, `better-sqlite3`'s prebuilt binary mismatches the ABI and the service can crash-loop; the error only mentioned `npm rebuild better-sqlite3` (which fails for global / no-toolchain installs). The startup + SQLite error hints now also point to the existing self-heal command `omniroute runtime repair` (rebuilds into a user-writable runtime), and a top-level `omniroute repair` alias was added. ([#3476](https://github.com/diegosouzapw/OmniRoute/issues/3476) — thanks @Rahulsharma0810) -- **fix(antigravity): per-request Pro-family upstream-id fallback chain (`gemini-3.1-pro-high` 400)** — Antigravity silently renamed the Gemini 3.1 Pro-high upstream id, so `gemini-3.1-pro-high` started returning HTTP 400 (while `-low` still worked) and the live id can't be determined statically (competitor proxies disagree). The executor now retries alternative ids on a 400 (`gemini-3.1-pro-high` → `gemini-pro-agent` → `gemini-3-pro-high`, analogous for pro-low), bounded and only on a 400, with zero extra cost on the happy path; the 1:1 tier-passthrough invariant is preserved (the chain is request-time, not a static alias remap). ([#3786](https://github.com/diegosouzapw/OmniRoute/issues/3786) — thanks @aliaksandrsen) -- **fix(sse): retry once on an early stream close (`STREAM_EARLY_EOF`) for single-model requests** — flaky OpenAI-compatible upstreams (e.g. NVIDIA NIM with minimax-m3 / qwen3.5 / glm-5.1) intermittently send HTTP 200 then close the SSE with zero useful frames, surfacing as a 502 "Stream ended before producing useful content". Only Antigravity got an early-close retry; every other provider returned the 502 immediately on the non-combo single-model path. A bounded one-retry (early-close only — not readiness-timeout — and without marking the account unavailable) now generalizes it. (The separate qwen-web validation SSRF part of the same report was already fixed in v3.8.24, [#3767](https://github.com/diegosouzapw/OmniRoute/pull/3767).) ([#3758](https://github.com/diegosouzapw/OmniRoute/issues/3758) — thanks @Svatosalav) -- **fix(models): preserve eye-hidden models across auto-sync / import** — hiding models via the visibility (eye) toggle to keep only a combo's models was undone on every model import or auto-sync, which re-showed all of them. The sync re-import treated "hidden" identically to "deleted" and dropped both; a distinct `isDeleted` marker now separates the trash/delete path (still dropped on re-import, #3199) from the eye toggle (preserved as listed-but-hidden), and eye-hidden models are no longer re-aliased into the routable catalog on sync. ([#3782](https://github.com/diegosouzapw/OmniRoute/issues/3782) — thanks @xenstar) -- **fix(providers): correct the lmarena cookie hint (`session` → `arena-auth-prod-v1`)** — the lmarena credential hint asked for a cookie named `session`, but lmarena.ai's real auth cookie is `arena-auth-prod-v1`, so users who pasted only `session=…` hit validation failures. The credential name, placeholder and storage keys now use the correct name (the legacy `session` key is retained for back-compat with already-saved credentials). ([#3810](https://github.com/diegosouzapw/OmniRoute/issues/3810) — thanks @xspylol) -- **fix(reasoning): normalize OpenAI-compatible `max` effort to `xhigh` by default** — OpenAI-compatible providers do not accept literal `max`, but some upstreams (for example DeepSeek through OpenRouter) support `xhigh`; `max` now maps to `xhigh` unless the target model explicitly opts out of `xhigh`, with Claude alias variants still honoring the canonical Claude opt-out list. ([#3826](https://github.com/diegosouzapw/OmniRoute/pull/3826) — thanks @rdself) -- **fix(combo): return the replay response on the round-robin streaming path** — a round-robin combo with a streaming target returned a body already locked by the readiness peek, surfacing as a 500 "ReadableStream is locked"; the round-robin path now returns the replay clone like the priority path does. ([#3811](https://github.com/diegosouzapw/OmniRoute/pull/3811) — thanks @0xtbug) -- **fix(claude): strip the reasoning-effort suffix from Claude model ids** — Claude ids carrying an effort suffix (`…-low` … `…-max`) 404'd upstream and tripped the circuit breaker into a misleading "rate-limited" state; the suffix is now stripped before dispatch. ([#3807](https://github.com/diegosouzapw/OmniRoute/pull/3807) — thanks @zhiru) -- **fix(sse): flush routed SSE chunks promptly (ping/zombie readiness filter)** — combo stream-readiness now filters ping/zombie frames so routed SSE chunks stream out without waiting on the readiness window. ([#3759](https://github.com/diegosouzapw/OmniRoute/pull/3759) — thanks @rdself) -- **fix(models): don't auto-hide transient (rate-limited / timeout) failures on Test All** — a parallel Test All across many models could rate-limit an account and auto-hide every model that 429'd / timed out (dropping them from `/v1/models`); transient failures now surface an error state but stay visible. ([#3849](https://github.com/diegosouzapw/OmniRoute/pull/3849) — thanks @lukmanc405) -- **fix(quota): surface OpenCode Go's missing quota-API as a latched diagnostic** — OpenCode Go keys whose quota endpoints return 404/401 no longer hammer the dead endpoints; the gap is latched with a clear message and an `OMNIROUTE_OPENCODE_GO_QUOTA_URL` override hint. ([#3838](https://github.com/diegosouzapw/OmniRoute/pull/3838) — thanks @adivekar-utexas) -- **fix(pricing): add the missing Kiro model pricing rows** — Kiro models the registry serves (e.g. `claude-sonnet-4.6`) had no pricing row and reported $0.00; the rows were added. ([#3835](https://github.com/diegosouzapw/OmniRoute/pull/3835) — thanks @artickc) -- **fix(ui): render country flags via flagcdn SVGs for Windows compatibility** — Windows doesn't render regional-indicator flag emoji; flags now use flagcdn SVGs with an emoji fallback. ([#3814](https://github.com/diegosouzapw/OmniRoute/pull/3814) — thanks @rafacpti23) -- **fix(ui): expand the request log table with a vertical resize handle** — the request log table now shows ~10 rows and can be resized vertically. ([#3820](https://github.com/diegosouzapw/OmniRoute/pull/3820) — thanks @rafacpti23) -- **fix(i18n): translate the missing `embeddedServices` keys across 37 locales** — the `embeddedServices` strings showed `__MISSING__` in 37 locales; they are now translated. ([#3819](https://github.com/diegosouzapw/OmniRoute/pull/3819) — thanks @rafacpti23) +- **test(oauth): 验证真实 gemini-cli / antigravity 分发路径的 refresh_token 保留** — #3679/#3766 回归测试使用了一个合成服务商,该服务商通过通用的 `tokenUrl` 路径进行路由,因此该修复从未针对实际 Google 系列服务商进行过验证——后者通过 `refreshGoogleToken()` 针对硬编码的 `OAUTH_ENDPOINTS.google.token` 进行分发。新增一个测试,通过真实的 `gemini-cli`/`antigravity` 路径驱动 `checkConnection`(将 Google 令牌端点重定向到返回 `invalid_grant` 的本地服务器),并断言 `refresh_token` 被保留(而非置空)——从而确认这些连接不会在刷新失败时被错误地销毁。 ([#3850](https://github.com/diegosouzapw/OmniRoute/issues/3850) — 感谢 @3xa228148) +- **fix(oauth): 为 GitLab Duo 提供清晰的设置提示,而非 "Internal server error"** — 在没有注册 OAuth 客户端的情况下添加 GitLab Duo 连接,在添加连接步骤中会返回一个含糊的 `Internal server 错误`。当 `GITLAB_DUO_OAUTH_CLIENT_ID` 缺失时,`buildAuthUrl` **抛出异常**,路由将其吞并为通用 500 错误。现返回 `null`(与 Qoder 服务商行为一致),授权路由会显示可操作的提示:在 `https://gitlab.com/-/profile/applications` 注册 OAuth 应用,重定向 URI 为 `http://localhost:20128/callback`,权限范围为 `ai_features read_user`,然后设置 `GITLAB_DUO_OAUTH_CLIENT_ID`。 ([#3861](https://github.com/diegosouzapw/OmniRoute/issues/3861) — 感谢 @sidinsearch) +- **fix(db): 持久化"保留最近备份"设置** — 在设置 → 数据库备份保留中更改备份保留数量无效:刷新后总是跳回 20(并且启动后编辑 `.env` 也被忽略,因为 `process.env` 不会被重新加载)。`getDbBackupMaxFiles()` 仅读取 `DB_BACKUP_MAX_FILES` 环境变量——没有 setter 也没有持久化值。该值现通过专用的 `key_value` 存储进行往返(`getDbBackupMaxFiles` 优先级:环境变量覆盖 → 持久化的界面值 → 默认 20),并且"清理旧备份"操作会持久化所选数量。现有安装保留历史默认值 20,直到显式更改。 ([#3834](https://github.com/diegosouzapw/OmniRoute/issues/3834) — 感谢 @netstratego) +- **fix(sse): 将 Gemini 思考预算限制在模型真实上限内(`reasoning_effort`/`effort=high` 400)** — 将 OpenAI `reasoning_effort=high`(以及 Claude-Code `output_config.effort=high`)翻译到 Gemini 目标时,发送了硬编码的 `thinkingBudget: 32768`,这超过了 Flash 级别 Gemini 的真实上限 24576 → 上游 HTTP 400(`thinkingLevel=high` 路径已经使用 24576 并且在相同模型上正常工作)。`gemini-2.5-flash` 现声明其真实 `thinkingBudgetCap`(24576),使现有的 `capThinkingBudget()` 截断点真正生效,并且 Claude→Gemini 的 `output_config.effort` 路径——此前直接发送原始值完全没有截断——现通过相同的截断点(pro 级别,真实上限 32768,保持不变)。 ([#3842](https://github.com/diegosouzapw/OmniRoute/issues/3842) — 感谢 @andrea-kingautomation) +- **fix(intelligence): 从实时启动路径运行定价 + models.dev 同步** — 与 Arena ELO 同步(v3.8.24)类似,外部**定价同步**(`PRICING_SYNC_ENABLED`)和**模型.dev 能力同步**(设置 → AI 开关)仅从 `server-init.ts` 初始化,而 Next 独立运行时永远不会执行该文件——并且模型.dev 完全没有调用者。它们的开关在生产环境中无效。两者现从 `instrumentation-node.ts` 初始化(自门控、保留可选择性、非阻塞、永不致命)。(感谢 @diegosouzapw) +- **test(proxy): 保护每连接"直连"绕过全局代理 + 更清晰的标签** — 每连接"代理关闭"开关(`proxyEnabled: false`)已经覆盖了已配置的**全局**代理(`resolveProxyForConnection` 在全局步骤之前短路到 `level: "direct"`)。新增一个显式回归测试,证明该绕过优先于全局分配(并在重新启用时可往返),并将界面标签改为"直连(绕过代理)",以便运维人员识别。关闭 [#2996](https://github.com/diegosouzapw/OmniRoute/issues/2996) 中的验证缺口。(感谢 @diegosouzapw) +- **feat(connections): 每连接"禁用冷却"退出机制** — 连接现可选择退出瞬时冷却(`providerSpecificData.disableCooling`,在编辑连接弹窗中带开关)。设置后,可恢复的故障仍会记录错误/退避,但**不会**将该连接移出轮转,因此它仍然有资格被选中——适用于你永远不希望因短暂波动而被搁置的主密钥。终端状态(封禁 / 过期 / 额度耗尽)仍然生效。 ([#2997](https://github.com/diegosouzapw/OmniRoute/issues/2997) — 感谢 @diegosouzapw) +- **fix(combo): 恢复无会话组合粘性 + 推理感知就绪(v3.8.14 后的 504 / TPS 回归)** — #3399(v3.8.16)将 `` 标签组合固定替换为基于客户端 `sessionId` 门控的服务端上下文缓存固定。不发送会话 ID 的客户端(大多数 OpenAI 兼容工具)丢失了组合粘性,导致组合每轮都重新运行策略选择 → 上游提示缓存未命中 → 冷高推理启动(~78s)→ 间歇性 `[504] Upstream request did not return response headers` + TPS 崩溃(仅发生在组合上)。固定现在在没有会话 ID 时回退到稳定的每对话指纹(`extractSessionAffinityKey(body)`)——**仅当 `context_cache_protection` 开启时**,因此 #3399 的防泄漏行为得以保留。另外,流就绪窗口现在**无条件**为高推理 Codex GPT-5.x 授予 +30s 推理预算(小型高推理提示无论粘性如何,在 80s 基准下都会 504)。 ([#3825](https://github.com/diegosouzapw/OmniRoute/issues/3825) — 感谢 @bypanghu) +- **test(combo): 覆盖 `skipProviderBreaker` 消费者门控** — 生产者已测试,但消费者(失败的 Combo 目标是否会触发全服务商熔断器)未测试;熔断器决策现为导出的纯谓词(`shouldRecordProviderBreakerFailure`,行为一致),并有直接测试断言 `connection_cooldown` 503 不会触发熔断器,而普通 503 会。关闭 [#2743](https://github.com/diegosouzapw/OmniRoute/issues/2743) 中的另一个延期测试缺口。(感谢 @diegosouzapw) +- **fix(providers): 显示真实的 Devin 错误 + 修正 Windsurf 认证说明** — Devin 聊天返回通用 502 "Invalid SSE response for non-streaming request",吞没了真实原因(例如"Devin CLI not found"):仅含错误的 SSE 块(无 `choices`)现会传播其经过清洗的消息。Windsurf "Visit windsurf.com/show-auth-token" 说明(裸 URL 在没有 IDE 提供的 `?state=` 时不会显示令牌)现引导用户使用 `Windsurf: Provide Auth Token` 命令面板流程。 ([#3324](https://github.com/diegosouzapw/OmniRoute/issues/3324) — 感谢 @mikmaneggahommie) +- **fix(grok-web): 针对反机器人 / IP 信誉拦截的更清晰 403 消息** — 从被标记的数据中心/VPS IP 验证 Grok Web 订阅时收到的 403 看起来像无效 cookie,导致用户去追查一个实际上正常的 cookie。非认证 403(Cloudflare 挑战 / 反机器人响应体)现返回消息说明 cookie 可能正常,拦截是基于 IP 信誉的——请从住宅 IP 重试或配置代理(认证类型的 403 保留重新粘贴指引)。 ([#3474](https://github.com/diegosouzapw/OmniRoute/issues/3474) — 感谢 @friedtofu1608) +- **fix(db): 使大量待处理迁移安全阈值可通过环境变量覆盖** — 从旧版本恢复备份数据库可能触发"检测到 N 个待处理迁移 … 阈值为 50",且无法覆盖硬编码的 `50`。该阈值现可通过 `OMNIROUTE_MAX_PENDING_MIGRATIONS` 配置(启动时解析;`0` 禁用检查)。 ([#3416](https://github.com/diegosouzapw/OmniRoute/issues/3416) — 感谢 @samuraiIT) +- **test(proxy): 覆盖 Vercel 中继 `proxyFetch` 路径** — 为 `buildVercelRelayHeaders` 和 `vercel` 类型中继短路(`x-中继-target`/`-path`/`-认证`、TCP 跳过、缺失认证 fail-closed)新增测试,关闭 [#2743](https://github.com/diegosouzapw/OmniRoute/issues/2743) 中跟踪的延期测试缺口之一。(感谢 @diegosouzapw) +- **fix(cli): 在原生模块错误消息中提示 `omniroute runtime repair`** — Node 主版本升级后,`better-sqlite3` 的预编译二进制与 ABI 不匹配,服务可能崩溃循环;错误仅提示 `npm rebuild better-sqlite3`(对全局/无工具链安装无效)。启动和 SQLite 错误提示现也指向已有的自修复命令 `omniroute runtime repair`(重建到用户可写的运行时),并添加了顶层 `omniroute repair` 别名。 ([#3476](https://github.com/diegosouzapw/OmniRoute/issues/3476) — 感谢 @Rahulsharma0810) +- **fix(antigravity): 每请求 Pro 系列上游 ID 容灾链(`gemini-3.1-pro-high` 400)** — Antigravity 静默重命名了 Gemini 3.1 Pro-high 上游 ID,导致 `gemini-3.1-pro-high` 开始返回 HTTP 400(而 `-low` 仍然正常),且实时 ID 无法静态确定(竞争对手代理意见不一)。执行器现对 400 重试备用 ID(`gemini-3.1-pro-high` → `gemini-pro-agent` → `gemini-3-pro-high`,pro-low 同理),有限且仅在 400 时触发,正常路径零额外开销;1:1 层级直通不变量得以保留(链是请求时的,而非静态别名映射)。 ([#3786](https://github.com/diegosouzapw/OmniRoute/issues/3786) — 感谢 @aliaksandrsen) +- **fix(sse): 对单模型请求的早期流关闭(`STREAM_EARLY_EOF`)重试一次** — 不稳定的 OpenAI 兼容上游(例如 NVIDIA NIM 搭配 minimax-m3 / qwen3.5 / glm-5.1)间歇性发送 HTTP 200 然后以零有效帧关闭 SSE,表现为 502 "Stream ended before producing useful content"。此前只有 Antigravity 获得了早期关闭重试;所有其他服务商在非 Combo 单模型路径上立即返回 502。现通过有限的一次重试(仅早期关闭——非就绪超时——且不标记账户不可用)进行泛化。(同一报告中独立的 qwen-web 验证 SSRF 部分已在 v3.8.24 中修复,[#3767](https://github.com/diegosouzapw/OmniRoute/pull/3767)。) ([#3758](https://github.com/diegosouzapw/OmniRoute/issues/3758) — 感谢 @Svatosalav) +- **fix(models): 在自动同步/导入中保留已隐藏模型** — 通过可见性(眼睛)开关隐藏模型以仅保留 Combo 模型的操作,在每次模型导入或自动同步时被撤销,所有模型重新显示。同步重新导入将"隐藏"等同于"删除"并丢弃两者;现通过独立的 `isDeleted` 标记将垃圾/删除路径(重新导入时仍丢弃,#3199)与眼睛开关(保留为已列出但隐藏)区分开来,且已隐藏模型不再在同步时重新别名为可路由目录。 ([#3782](https://github.com/diegosouzapw/OmniRoute/issues/3782) — 感谢 @xenstar) +- **fix(providers): 修正 lmarena cookie 提示(`session` → `arena-auth-prod-v1`)** — lmarena 凭证提示要求名为 `会话` 的 cookie,但 lmarena.ai 的真实认证 cookie 是 `arena-认证-prod-v1`,因此仅粘贴 `会话=…` 的用户会遇到校验失败。凭证名称、占位符和存储密钥现已使用正确的名称(保留旧版 `会话` 密钥以向后兼容已保存的凭证)。 ([#3810](https://github.com/diegosouzapw/OmniRoute/issues/3810) — 感谢 @xspylol) +- **fix(reasoning): 默认将 OpenAI 兼容的 `max` 强度标准化为 `xhigh`** — OpenAI 兼容服务商不接受字面量 `max`,但部分上游(例如通过 OpenRouter 的 DeepSeek)支持 `xhigh`;`max` 现映射为 `xhigh`,除非目标模型明确退出 `xhigh`,Claude 别名变体仍然遵循规范的 Claude 退出列表。 ([#3826](https://github.com/diegosouzapw/OmniRoute/pull/3826) — 感谢 @rdself) +- **fix(combo): 在轮询流式路径上返回重放响应** — 带有流式传输目标的轮询 Combo 返回了一个已被就绪探测锁定的响应体,表现为 500 "ReadableStream is locked";轮询路径现像优先级路径一样返回重放克隆。 ([#3811](https://github.com/diegosouzapw/OmniRoute/pull/3811) — 感谢 @0xtbug) +- **fix(claude): 从 Claude 模型 ID 中去除推理强度后缀** — 带有强度后缀的 Claude ID(`…-low` … `…-max`)在上游返回 404 并触发熔断器进入误导性的"速率受限"状态;后缀现已在分发前去除。 ([#3807](https://github.com/diegosouzapw/OmniRoute/pull/3807) — 感谢 @zhiru) +- **fix(sse): 及时刷新已路由的 SSE 块(ping/僵尸就绪过滤器)** — Combo 流就绪现过滤 ping/僵尸帧,使已路由的 SSE 块无需等待就绪窗口即可流出。 ([#3759](https://github.com/diegosouzapw/OmniRoute/pull/3759) — 感谢 @rdself) +- **fix(models): 不要自动隐藏 Test All 中的瞬时(速率限制/超时)失败** — 跨多个模型的并行 Test All 可能对账户造成速率限制,并自动隐藏每个 429/超时的模型(将其从 `/v1/模型` 中移除);瞬时失败现显示错误状态但保持可见。 ([#3849](https://github.com/diegosouzapw/OmniRoute/pull/3849) — 感谢 @lukmanc405) +- **fix(quota): 将 OpenCode Go 缺失的配额 API 显示为锁定诊断** — 配额端点返回 404/401 的 OpenCode Go 密钥不再反复请求已失效的端点;该缺口被锁定并附带清晰消息和 `OMNIROUTE_OPENCODE_GO_QUOTA_URL` 覆盖提示。 ([#3838](https://github.com/diegosouzapw/OmniRoute/pull/3838) — 感谢 @adivekar-utexas) +- **fix(pricing): 添加缺失的 Kiro 模型定价行** — 注册表提供的 Kiro 模型(例如 `claude-sonnet-4.6`)没有定价行并报告 $0.00;已添加相应行。 ([#3835](https://github.com/diegosouzapw/OmniRoute/pull/3835) — 感谢 @artickc) +- **fix(ui): 通过 flagcdn SVG 渲染国旗以兼容 Windows** — Windows 不渲染区域指示符国旗 emoji;国旗现使用 flagcdn SVG 并附带 emoji 回退。 ([#3814](https://github.com/diegosouzapw/OmniRoute/pull/3814) — 感谢 @rafacpti23) +- **fix(ui): 使用垂直调整手柄扩展请求日志表** — 请求日志表现在显示约 10 行,并可垂直调整大小。 ([#3820](https://github.com/diegosouzapw/OmniRoute/pull/3820) — 感谢 @rafacpti23) +- **fix(i18n): 翻译 37 个语言环境中缺失的 `embeddedServices` 键** — `embeddedServices` 字符串在 37 个语言环境中显示为 `__MISSING__`;现已翻译。 ([#3819](https://github.com/diegosouzapw/OmniRoute/pull/3819) — 感谢 @rafacpti23) -### 🔒 Security & Hardening +### 🔒 安全与加固 -- **fix(security): CCR cross-tenant IDOR — per-principal scope store + bounded memory** — the compression CCR scope store was shared across principals, allowing cross-tenant reads; it is now scoped per-principal with bounded memory. ([#3859](https://github.com/diegosouzapw/OmniRoute/pull/3859)) -- **feat(supply-chain): build provenance, SBOM, Trivy scan & OpenSSF Scorecard (advisory)** — added npm build provenance, a CycloneDX SBOM, Trivy image scanning, and an OpenSSF Scorecard workflow (Quality Gates Fase 8 · Bloco A, advisory). ([#3824](https://github.com/diegosouzapw/OmniRoute/pull/3824)) +- **fix(security): CCR 跨租户 IDOR — 按主体划分的作用域存储 + 有界内存** — 压缩 CCR 作用域存储此前在所有主体之间共享,允许跨租户读取;现已按主体划分作用域并设置内存边界。 ([#3859](https://github.com/diegosouzapw/OmniRoute/pull/3859)) +- **feat(supply-chain): 构建来源、SBOM、Trivy 扫描 & OpenSSF Scorecard(建议性)** — 添加了 npm 构建来源、CycloneDX SBOM、Trivy 镜像扫描和 OpenSSF Scorecard 工作流(Quality Gates Fase 8 · Bloco A,建议性)。 ([#3824](https://github.com/diegosouzapw/OmniRoute/pull/3824)) -### 🧹 Internal / Quality / Docs +### 🧹 内部 / 质量 / 文档 -- **Consolidate the email-privacy control into Settings → Appearance** — the per-page email-privacy toggles were replaced by a single global switch. ([#3822](https://github.com/diegosouzapw/OmniRoute/pull/3822) — thanks @rdself) -- **docs(ui): clarify the routing-settings copy (strategy sync + sticky limit)** — ([#3843](https://github.com/diegosouzapw/OmniRoute/pull/3843) — thanks @adivekar-utexas) -- **Quality Gates — Fase 7 & 8** — promoted the dead-code / cognitive-complexity / type-coverage ratchets to blocking, installed advisory CI scanners (gitleaks / osv / actionlint / zizmor), and added property + golden + SSE-correctness tests and a runtime-resilience (chaos / heap-growth / k6 soak) suite. ([#3809](https://github.com/diegosouzapw/OmniRoute/pull/3809), [#3858](https://github.com/diegosouzapw/OmniRoute/pull/3858), [#3808](https://github.com/diegosouzapw/OmniRoute/pull/3808), [#3854](https://github.com/diegosouzapw/OmniRoute/pull/3854)) -- **fix(docs): add MDX frontmatter to `SUPPLY_CHAIN.md`** — the new security doc lacked the `title:` frontmatter that MDX pages require, which broke the production Build + Docker Hub publish; the frontmatter was added. ([#3864](https://github.com/diegosouzapw/OmniRoute/pull/3864)) -- **chore(deps): bump `aquasecurity/trivy-action` 0.28.0 → 0.36.0** ([#3862](https://github.com/diegosouzapw/OmniRoute/pull/3862)) -- **chore(quality): reconcile the file-size ratchet baseline for Prettier-inflated v3.8.25 fixes + `chat.ts` growth** — the per-file size baseline was re-frozen to absorb the formatting/line-count growth from this cycle's chat-core and combo fixes (manual edits, never an automatic upward ratchet). ([#3823](https://github.com/diegosouzapw/OmniRoute/pull/3823), [#3833](https://github.com/diegosouzapw/OmniRoute/pull/3833) — thanks @diegosouzapw) -- **test(suite): green the unit suite at release time — align stale tests to this cycle's intended behavior + de-flake two new suites** — release-gate housekeeping: updated tests that lagged behind intended behavior changes (OpenCode Go latched quota message #3838, the email-privacy control consolidated into Settings #3822, SOCKS5 default-on proxy-type message, the `[id]` provider-detail strangler-fig decomposition #3501, Vertex Express-mode keys, Antigravity discovery using a current user-callable model id) and the same-provider 503 fall-through resilience test; de-flaked the compression benchmark reproducibility test (sequential passes) and the ServiceSupervisor crash test (poll instead of fixed sleep). No production code changed. Also documented `OMNIROUTE_MAX_PENDING_MIGRATIONS` (#3416) in `.env.example` + `ENVIRONMENT.md`. (thanks @diegosouzapw) +- **将邮箱隐私控制合并到设置 → 外观** — 每个页面的邮箱隐私开关被替换为单一的全局开关。 ([#3822](https://github.com/diegosouzapw/OmniRoute/pull/3822) — 感谢 @rdself) +- **docs(ui): 澄清路由设置文案(策略同步 + 粘性限制)** — ([#3843](https://github.com/diegosouzapw/OmniRoute/pull/3843) — 感谢 @adivekar-utexas) +- **Quality Gates — Fase 7 & 8** — 将死代码 / 认知复杂度 / 类型覆盖率门槛提升为阻塞项,安装建议性 CI 扫描器(gitleaks / osv / actionlint / zizmor),并添加属性 + 黄金 + SSE 正确性测试以及运行时韧性(混沌 / 堆增长 / k6 压力)套件。 ([#3809](https://github.com/diegosouzapw/OmniRoute/pull/3809), [#3858](https://github.com/diegosouzapw/OmniRoute/pull/3858), [#3808](https://github.com/diegosouzapw/OmniRoute/pull/3808), [#3854](https://github.com/diegosouzapw/OmniRoute/pull/3854)) +- **fix(docs): 为 `SUPPLY_CHAIN.md` 添加 MDX frontmatter** — 新的安全文档缺少 MDX 页面所需的 `title:` frontmatter,导致生产构建和 Docker Hub 发布失败;已添加 frontmatter。 ([#3864](https://github.com/diegosouzapw/OmniRoute/pull/3864)) +- **chore(deps): 升级 `aquasecurity/trivy-action` 0.28.0 → 0.36.0** ([#3862](https://github.com/diegosouzapw/OmniRoute/pull/3862)) +- **chore(quality): 为 Prettier 膨胀的 v3.8.25 修复 + `chat.ts` 增长调整文件大小基准** — 重新冻结每文件大小基准,以吸收本周期聊天核心和 Combo 修复带来的格式化/行数增长(手动编辑,绝不自动上调)。 ([#3823](https://github.com/diegosouzapw/OmniRoute/pull/3823), [#3833](https://github.com/diegosouzapw/OmniRoute/pull/3833) — 感谢 @diegosouzapw) +- **test(suite): 发布时确保单元套件通过 — 将过期测试对齐到本周期的预期行为 + 修复两个新套件的波动性** — 发布门槛维护:更新了落后于预期行为变更的测试(OpenCode Go 锁定配额消息 #3838、邮箱隐私控制合并到设置 #3822、SOCKS5 默认开启代理类型消息、`[id]` 服务商详情绞杀榕分解 #3501、Vertex Express-mode 密钥、使用当前用户可调用模型 ID 的 Antigravity 发现)以及同服务商 503 穿透韧性测试;修复了压缩基准可重复性测试的波动性(连续通过)和 ServiceSupervisor 崩溃测试的波动性(轮询替代固定睡眠)。无生产代码变更。同时将 `OMNIROUTE_MAX_PENDING_MIGRATIONS`(#3416)记录到 `.env.example` + `ENVIRONMENT.md` 中。(感谢 @diegosouzapw) --- @@ -1675,301 +1693,284 @@ _Development cycle in progress._ ## [3.8.8] — 2026-06-01 -### Added +### 新增 -- **Plugins framework** (`src/lib/plugins/`, `/api/plugins/*`, `/dashboard/plugins`) — hooks + registry unification, plugin SDK (`definePlugin`), worker-thread sandbox, per-plugin hook rate limiting, SHA-256 integrity verification, semver-gated upgrade, and execution analytics. Plugin routes are loopback-only (`isLocalOnlyPath`) and `child_process` exec is opt-in via `OMNIROUTE_PLUGINS_ALLOW_EXEC`. (#2913 / #3041 — thanks @oyi77) -- **Plugin system: response-hook wiring + startup load + example plugin** — wires the plugin `onResponse` hook into the chat success path, loads active plugins on server startup so they survive restarts (`pluginManager.loadAll()` in `server-init`), and ships a `welcome-banner` example plugin (`examples/plugins/`) plus a comprehensive plugin test suite. (#3045 — thanks @oyi77) -- **API key option: disable non-published models** — a per-key flag restricting the key to discovered, public models (combos / `auto/*` / `qtSd/*` routing still allowed). (#3017 — thanks @androw) -- **SessionPool — modular & provider-agnostic** (`open-sse/services/sessionPool/`) — pooled - cookie/session manager with round-robin fingerprint rotation (distinct fingerprint per pooled - session), per-session cooldown/backoff, and a provider-agnostic `webExecutorWrapper`. Adds pool - support for DuckDuckGo Web and LLM7 providers and an MCP `poolTools` toolset. (#2954 / #2978 — thanks @oyi77) -- **AgentBridge** (`/dashboard/tools/agent-bridge`) — MITM proxy consolidating 9 IDE agents +- **插件框架** (`src/lib/plugins/`, `/api/plugins/*`, `/dashboard/plugins`) — 钩子 + 注册表统一,插件 SDK (`definePlugin`),工作线程沙箱,每插件钩子速率限制,SHA-256 完整性校验,语义化版本升级管控,执行分析。插件路由仅限本地回环 (`isLocalOnlyPath`),且 `child_process` exec 需通过 `OMNIROUTE_PLUGINS_ALLOW_EXEC` 显式启用。(#2913 / #3041 — 感谢 @oyi77) +- **插件系统: 响应钩子接线 + 启动加载 + 示例插件** — 将插件 `onResponse` 钩子接入聊天成功路径,在服务器启动时加载活跃插件使其在重启后存活(`pluginManager.loadAll()` 在 `server-init` 中),附带 `welcome-banner` 示例插件 (`examples/plugins/`) 以及全面的插件测试套件。(#3045 — 感谢 @oyi77) +- **API 密钥选项: 禁用非公开模型** — 每密钥标志,限制密钥只能访问已发现、公开的模型(combos / `auto/*` / `qtSd/*` 路由仍允许)。(#3017 — 感谢 @androw) +- **会话池 — 模块化且��服务商无关** (`open-sse/services/sessionPool/`) — 池化 + cookie/会话管理器,支持轮询指纹轮换(每个池化会话有不同指纹), + 每会话冷却/退避,与服务商无关的 `webExecutorWrapper`。为 DuckDuckGo Web 和 LLM7 + 服务商添加了池化支持,以及 MCP `poolTools` 工具集。(#2954 / #2978 — 感谢 @oyi77) +- **AgentBridge** (`/dashboard/tools/agent-bridge`) — MITM(中间人代理),整合 9 个 IDE 智能体 (Antigravity, Kiro, GitHub Copilot, OpenAI Codex, Cursor IDE, Zed Industries, Claude Code, - Open Code, Trae stub) with server card, per-agent setup wizard, model mapping table, - bypass list, upstream CA cert support, and redirect from legacy `/dashboard/system/mitm-proxy`. - See `docs/frameworks/AGENTBRIDGE.md`. (#2858 — thanks @diegosouzapw) -- **Traffic Inspector** (`/dashboard/tools/traffic-inspector`) — LLM-aware HTTPS debugger with - 4 capture modes (AgentBridge hook, Custom Hosts DNS, HTTP_PROXY :8080, System-wide proxy), - DevTools split UI, 7 detail tabs (Conversation, Headers, Request, Response, Timing, LLM Details, - Stats), resizable panels, session recording (.har/.jsonl export), SSE stream merger, - conversation normalizer (multi-provider), system-prompt fingerprint colorization, and annotations. - See `docs/frameworks/TRAFFIC_INSPECTOR.md`. -- **MITM handler base + 9 agent handlers** (`src/mitm/handlers/`) — `MitmHandlerBase` abstract - class with `hookBufferStart`/`hookBufferUpdate` for Traffic Inspector integration; concrete - handlers for all 9 agents. -- **MITM targets registry** (`src/mitm/targets/`) — declarative `MitmTarget` shape per agent; - emits `DATA_DIR/mitm/targets.json` for dynamic `server.cjs` resolution. -- **Traffic Inspector core** (`src/mitm/inspector/`) — `TrafficBuffer` in-memory ring, - `kindDetector`, `sseMerger` (MIT port from chouzz/llm-interceptor), `conversationNormalizer` - (MIT port), `contextKey` fingerprinting, `httpProxyServer`, `systemProxyConfig`. -- **AgentBridge passthrough + bypass** (`src/mitm/passthrough.ts`) — TCP tunnel for - non-mapped hosts; bypass list with default sensitive-host patterns + user-defined patterns. -- **Upstream CA cert** (`src/mitm/upstreamTrust.ts`) — `AGENTBRIDGE_UPSTREAM_CA_CERT` for - corporate TLS environments. -- **Secret masking** (`src/mitm/maskSecrets.ts`) — sk-/Bearer/generic token masking before - any log or Traffic Inspector broadcast. -- **DB migrations 073–075** — `agent_bridge_state`, `agent_bridge_mappings`, + Open Code, Trae 桩),提供服务卡片、每智能体设置向导、模型映射表、 + 绕过列表、上游 CA 证书支持,并从旧版 `/dashboard/system/mitm-proxy` 重定向。 + 详见 `docs/frameworks/AGENTBRIDGE.md`。(#2858 — 感谢 @diegosouzapw) +- **流量检查器** (`/dashboard/tools/traffic-inspector`) — LLM 感知的 HTTPS 调试器,支持 + 4 种捕获模式(AgentBridge 钩子、自定义 Hosts DNS、HTTP_PROXY :8080、系统级代理)、 + DevTools 分屏界面、7 个详情标签页(对话、请求头、请求、响应、耗时、LLM 详情、 + 统计)、可调整大小的面板、会话录制(.har/.jsonl 导出)、SSE 流合并器、 + 对话规范化器(跨服务商)、系统提示指纹着色及注释。 + 详见 `docs/frameworks/TRAFFIC_INSPECTOR.md`。 +- **MITM 处理器基类 + 9 个智能体处理器** (`src/mitm/handlers/`) — `MitmHandlerBase` 抽象 + 类,包含 `hookBufferStart`/`hookBufferUpdate` 用于流量检查器集成;以及全部 9 个智能体的具体处理器。 +- **MITM 目标注册表** (`src/mitm/targets/`) — 每个智能体的声明式 `MitmTarget` 形态; + 生成 `DATA_DIR/mitm/targets.json` 用于动态 `server.cjs` 解析。 +- **流量检查器核心** (`src/mitm/inspector/`) — `TrafficBuffer` 内存环形缓冲区、 + `kindDetector`、`sseMerger`(从 chouzz/llm-interceptor MIT 移植)、`conversationNormalizer` + (MIT 移植)、`contextKey` 指纹识别、`httpProxyServer`、`systemProxyConfig`。 +- **AgentBridge 透传 + 绕过** (`src/mitm/passthrough.ts`) — 为非映射主机提供 TCP 隧道; + 绕过列表包含默认敏感主机模式 + 用户自定义模式。 +- **上游 CA 证书** (`src/mitm/upstreamTrust.ts`) — `AGENTBRIDGE_UPSTREAM_CA_CERT` 用于 + 企业 TLS 环境。 +- **密钥脱敏** (`src/mitm/maskSecrets.ts`) — 在任何日志或流量检查器广播前,对 sk-/Bearer/通用令牌进行脱敏。 +- **数据库迁移 073–075** — `agent_bridge_state`, `agent_bridge_mappings`, `agent_bridge_bypass`, `inspector_custom_hosts`, `inspector_sessions`, `inspector_session_requests`. -- **~28 API routes** under `/api/tools/agent-bridge/` (12 routes) and - `/api/tools/traffic-inspector/` (16+ routes). All LOCAL_ONLY + SPAWN_CAPABLE. -- **i18n** PT-BR + EN for all new keys in `agentBridge.*` and `trafficInspector.*` namespaces; - all other locales fall back to EN automatically. -- **E2E smoke tests** — `tests/e2e/agent-bridge.spec.ts`, +- **约 28 个 API 路由** 位于 `/api/tools/agent-bridge/`(12 个路由)和 + `/api/tools/traffic-inspector/`(16+ 个路由)。全部为 LOCAL_ONLY + SPAWN_CAPABLE。 +- **国际化**:`agentBridge.*` 和 `trafficInspector.*` 命名空间下所有新增键的 PT-BR + EN 翻译; + 所有其他语言自动回退到 EN。 +- **端到端冒烟测试** — `tests/e2e/agent-bridge.spec.ts`, `tests/e2e/traffic-inspector.spec.ts`, `tests/e2e/agent-bridge-traffic-cross.spec.ts` - (skip-gated on CI by `RUN_AGENT_BRIDGE_E2E` / `RUN_TRAFFIC_INSPECTOR_E2E` / `RUN_CROSS_E2E`). -- **Documentation** — `docs/frameworks/AGENTBRIDGE.md` and `docs/frameworks/TRAFFIC_INSPECTOR.md`; - `docs/architecture/REPOSITORY_MAP.md` updated; `docs/reference/openapi.yaml` updated with - ~28 new routes and 20+ new schemas. -- **i18n:** translate Ukrainian (uk-UA) menu and UI strings, plus complete uk-UA UI coverage (#2981 / #2988 — thanks @Lion-killer) -- **providers:** add SiliconFlow endpoint selector (#2975 — thanks @xz-dev) -- **oauth:** add Trae SOLO provider (work/code modes) (#2964 — thanks @S0yora) -- **providers:** add Qwen Web (chat.qwen.ai) web-cookie provider (#2947 — thanks @oyi77) -- **Quota Share Engine — multi-provider quota pools** — Monitoring/Costs reorg plus a Quota Share Engine: group selector, grouped pool cards, exclusive-quota API keys (`allowedQuotas`), `quotaShared-*` routing models via combos, a 3-step pool wizard (legacy Plans page retired), endpoint + key preview, and full pool editing. Adds quota-pool DB migrations. (#2859 / #3022 / #3032 — thanks @diegosouzapw) -- **Dashboard page redesigns (Nav Restructure)** — agent-skills + omni-skills with a dynamic 42-skill catalog and MCP/A2A discovery (#2827); CLI Code's + CLI Agents + ACP Agents pages (#2839); translator friendly redesign, 5 tabs → 2 (#2847); functional `/batch` + `/batch/files` redesign (#2849); Playground Studio + Search Tools Studio (#2869); memory engine redesign — sqlite-vec + hybrid RRF + Studio UI (#2873). (thanks @diegosouzapw) -- **notion:** add Notion as an MCP context source — 6 tools (`notion_search`, `notion_list_databases`, `notion_get_database`, `notion_query_database`, `notion_read`, `notion_append_blocks`) scoped under `read:notion` / `write:notion`, with dashboard "Context Sources" tab, settings API, and token persistence in `key_value` table (#2959) + (在 CI 上由 `RUN_AGENT_BRIDGE_E2E` / `RUN_TRAFFIC_INSPECTOR_E2E` / `RUN_CROSS_E2E` 跳过控制)。 +- **文档** — `docs/frameworks/AGENTBRIDGE.md` 和 `docs/frameworks/TRAFFIC_INSPECTOR.md`; + 更新了 `docs/architecture/REPOSITORY_MAP.md`;更新了 `docs/reference/openapi.yaml`, + 新增约 28 个路由和 20+ 个 Schema。 +- **国际化:** 翻译乌克兰语(uk-UA)菜单和界面字符串,并完成完整的 uk-UA 界面覆盖 (#2981 / #2988 — 感谢 @Lion-killer) +- **服务商:** 添加 SiliconFlow 端点选择器 (#2975 — 感谢 @xz-dev) +- **OAuth:** 添加 Trae SOLO 服务商(work/code 模式)(#2964 — 感谢 @S0yora) +- **服务商:** 添加 Qwen Web(chat.qwen.ai)web-cookie 服务商 (#2947 — 感谢 @oyi77) +- **配额共享引擎 — 跨服务商配额池** — 监控/费用重构,外加配额共享引擎:分组选择器、分组池卡片、独占配额 API 密钥 (`allowedQuotas`)、通过 combos 的 `quotaShared-*` 路由模型、三步池向导(旧版 Plans 页面已退役)、端点 + 密钥预览以及完整的池编辑功能。新增配额池数据库迁移。(#2859 / #3022 / #3032 — 感谢 @diegosouzapw) +- **仪表盘页面重新设计(导航重构)** — 智能体技能 + omni 技能,带动态 42 技能目录和 MCP/A2A 发现 (#2827);CLI Code + CLI Agents + ACP Agents 页面 (#2839);翻译器友好重新设计,5 个标签页 → 2 个 (#2847);功能性 `/batch` + `/batch/files` 重新设计 (#2849);Playground Studio + Search Tools Studio (#2869);记忆引擎重新设计 — sqlite-vec + 混合 RRF + Studio 界面 (#2873)。(感谢 @diegosouzapw) +- **notion:** 添加 Notion 作为 MCP 上下文源 — 6 个工具 (`notion_search`、`notion_list_databases`、`notion_get_database`、`notion_query_database`、`notion_read`、`notion_append_blocks`),作用域为 `read:notion` / `write:notion`,配有仪表盘"上下文源"标签页、设置 API 和 `key_value` 表中的令牌持久化 (#2959) -### Changed +### 变更 -- Sidebar Tools group: added `agent-bridge` and `traffic-inspector` items after `cloud-agents`. -- `/api/tools/agent-bridge/` and `/api/tools/traffic-inspector/` added to `LOCAL_ONLY_API_PREFIXES` - and `SPAWN_CAPABLE_PREFIXES` in `src/server/authz/routeGuard.ts`. -- `.env.example`: documented 9 new env vars (`AGENTBRIDGE_UPSTREAM_CA_CERT`, - `INSPECTOR_BUFFER_SIZE`, `INSPECTOR_HTTP_PROXY_PORT`, `INSPECTOR_HTTP_PROXY_AUTOSTART`, - `INSPECTOR_TLS_INTERCEPT`, `INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES`, `INSPECTOR_MAX_BODY_KB`, - `INSPECTOR_MASK_SECRETS`, `INSPECTOR_LLM_HOSTS_EXTRA`, `INSPECTOR_INTERNAL_INGEST_TOKEN`). +- 侧边栏工具组:在 `cloud-agents` 之后添加了 `agent-bridge` 和 `traffic-inspector` 项。 +- `/api/tools/agent-bridge/` 和 `/api/tools/traffic-inspector/` 已添加到 `src/server/authz/routeGuard.ts` 中的 `LOCAL_ONLY_API_PREFIXES` 和 `SPAWN_CAPABLE_PREFIXES`。 +- `.env.example`:记录了 9 个新的环境变量(`AGENTBRIDGE_UPSTREAM_CA_CERT`、 + `INSPECTOR_BUFFER_SIZE`、`INSPECTOR_HTTP_PROXY_PORT`、`INSPECTOR_HTTP_PROXY_AUTOSTART`、 + `INSPECTOR_TLS_INTERCEPT`、`INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES`、`INSPECTOR_MAX_BODY_KB`、 + `INSPECTOR_MASK_SECRETS`、`INSPECTOR_LLM_HOSTS_EXTRA`、`INSPECTOR_INTERNAL_INGEST_TOKEN`)。 -### Fixed +### 已修复 -- **codex/providers:** `POST /api/providers/[id]/refresh` (the manual/auto "refresh - token" endpoint) no longer rotates rotating-refresh providers (Codex/OpenAI share - one Auth0 `client_id`). This was the last unguarded proactive-refresh entry point: - when the dashboard auto-refreshed every expiring connection on a page load (or an - old cached frontend bulk-called it), each Codex account's single-use refresh_token - was rotated, and Auth0 revoked the whole token family (`openai/codex#9648`) — every - account but the last died with `[403] `. The quota path now skips proactive refresh for - rotating providers (`rotationGroupFor`) and reuses the current access*token, - deferring genuine expiry to the reactive, serialized 401 path. Defense in - depth: `serializeRefresh` now leaves a settle gap between two \_queued* sibling - refreshes (default 2000 ms, tunable via `CODEX_REFRESH_SPACING_MS`, `"0"` to - opt out) while releasing a lone refresh immediately, so the reactive path adds - no latency. -- **payload-rules:** saved payload rules now survive a server restart. When no - in-memory override is set (fresh process before the boot hook ran, or a - separate module instance in the standalone build), `getPayloadRulesConfig` - now reads the DB-persisted rules (the source of truth) before the file config, - instead of silently returning the empty file default. (#2986) -- **models/custom:** custom models can now carry a per-model `targetFormat` - override (e.g. an opencode-go custom model that must use the Anthropic Messages - shape). Previously custom models always routed as OpenAI-compatible because - `targetFormat` was neither persisted nor consulted at routing time. Threaded - through `addCustomModel`/`replaceCustomModels`/`updateCustomModel`, the API - schema/route, `getModelInfo`, and chatCore's targetFormat resolution. (#2905) -- **providers/pollinations:** route to `gen.pollinations.ai/v1` instead of the - retired `text.pollinations.ai` host, which now returns `404 "legacy API"` for - all models. The gen gateway is the current OpenAI-compatible endpoint. (#2987) -- **executors/codex:** drop the CLI-injected `image_generation` hosted tool for - free-plan Codex accounts (`workspacePlanType === "free"`), which can't run it - server-side and would otherwise get an upstream 400. Paid plans keep it. - (mirrors CLIProxyAPI's free-plan guard; spun off from the #2980 analysis) -- **dashboard:** custom providers (`openai-compatible-*` / `anthropic-compatible-*`) - now show their user-given node name instead of the raw UUID id across the - active-requests panel, proxy logger, and home-page provider topology. The - display-label resolver was extracted into a shared util reused by all surfaces - (previously only the request-log viewer resolved it). (#2968) -- **docker:** the standalone launcher (Docker `CMD`) now honors - `OMNIROUTE_MEMORY_MB` (default 512, clamped [64, 16384]) and overrides the - image `NODE_OPTIONS` fallback, fixing random OOM crashes under load / with - large SQLite DBs. Previously only `omniroute serve` honored the knob. (#2939) -- **docker:** add a `web` compose profile (`omniroute-web`, target `runner-web`, - image `omniroute:web`) so web-cookie providers (gemini-web, claude-web, - claude-turnstile) work out of the box — the default `base` image ships without - Chromium/Playwright, which made those providers fail with - "Executable doesn't exist at .../ms-playwright/chromium...". (#2832) -- **routing/codex:** fix two gpt-5.5 Codex defects (#2877). (A) For a Codex-only - account, a bare `gpt-5.5` Responses request was rerouted to codex with the - model hardcoded to `gpt-5.5-medium` (`chatHelpers.ts`); the executor read that - `-medium` suffix as an explicit `modelEffort` that (per #2331) overrode a - client `reasoning.effort=xhigh`, silently demoting it — now it keeps the bare - `gpt-5.5` id so the client effort wins. (B) `gpt-5.5-xhigh`/`-high`/`-low` - misrouted to `openai` (→ "No credentials" for codex-only users); the suffixed - variants are now in `CODEX_PREFERRED_UNPREFIXED_MODELS` so they infer codex. -- **sse/chatCore:** remove a duplicate `const settings` declaration in - `handleChatCore` (introduced alongside the per-key stream-default-mode - feature). The same-scope redeclaration made esbuild/tsx fail with - "The symbol 'settings' has already been declared", which turned every unit - test that imports chatCore red and broke the production build. The earlier - consolidated `settings` const is now reused. -- **db/migrations:** resolve a `077` migration version collision - (`077_api_key_stream_default_mode.sql` vs `077_quota_pools.sql`) that made - `getMigrationFiles()` throw and blocked `getDbInstance()` at startup (app would - not boot; every DB-touching test was red). Renumbered the dependency-free, - idempotent `quota_pools` migration to `085`, kept the non-idempotent - `api_key_stream_default_mode` `ALTER` at `077`, added a retroactive - `isSchemaAlreadyApplied` guard (case `085`), and a regression test enforcing - unique migration prefixes. -- **routing/reasoning-replay:** OpenCode `big-pickle` (provider `opencode`/`oc` - and `opencode-zen`) now declares the interleaved `reasoning_content` contract - via a new `RegistryModel.interleavedField` field, so follow-up/tool-use turns - replay reasoning_content. Previously `big-pickle` matched no replay pattern and - failed with `[400] The reasoning_content in the thinking mode must be passed -back to the API` (its DeepSeek-thinking upstream is not detectable from the - model id, and `requiresReasoningReplay` does not consume `supportsReasoning`). - `getResolvedModelCapabilities` now surfaces the registry `interleavedField`. (#2900) -- **providers/github-copilot:** built-in GitHub Copilot Claude Opus and Gemini - models (`claude-opus-4.7`, `claude-opus-4-5-20251101`, `gemini-3.1-pro-preview`, - `gemini-3-flash-preview`) no longer carry `targetFormat: "openai-responses"`, so - they route through `chat/completions` (the provider default, like the working - `claude-opus-4.6`) instead of the Responses API, which Copilot does not serve for - non-OpenAI models (returned `[400]`). Native OpenAI `gpt-*` models keep the - Responses API. (#2911) -- **translator/responses:** Codex Desktop injects an `image_generation` hosted - tool into every Responses API request (even text-only ones), which OmniRoute - rejected with `[400] image_generation tool type is not supported`. It is now - treated like `tool_search`: allowed past the tool-type validator and dropped - silently from the tools array before forwarding to Chat Completions. (#2950) -- **combo/builder:** no-auth OpenCode Free combo entries now use the `oc/` routing - alias instead of the `opencode/` prefix. `parseModel("opencode/")` - resolves to the `opencode-zen` api-key tier (via a manual `ALIAS_TO_PROVIDER_ID` - override), so combos built with the bare provider id misrouted away from the - no-auth `opencode` provider; `oc/` resolves correctly. (#2901) -- **resilience/providers:** a route-restriction `403` (e.g. Fireworks Fire Pass - `fpk_*` keys returning "…not authorized for this route." on `/models`, while - chat still works) no longer marks the connection unavailable. Provider - validation falls through to the chat probe for such 403s instead of returning - "Invalid API key", and `checkFallbackError` short-circuits them to no cooldown. - Genuine auth failures (401 / generic 403) still fail fast. (#2929) -- **auth/opencode-zen:** the OpenCode Zen free model now works in the Playground - and combos without an API key. `opencode-zen` serves the public, signup-free - endpoint (`https://opencode.ai/zen/v1`); when no api-key connection is - configured, credential resolution now falls back to anonymous (no-auth) access - instead of failing with "No credentials for provider: opencode-zen". A - configured, active key is still used when present. (#2962) -- **translator/responses:** fixed an upstream `[400] Messages with role 'tool' -must be a response to a preceding message with 'tool_calls'` when a Codex - client sent a `function_call` with an empty/missing `call_id`. The orphaned - `function_call_output` previously slipped past the orphan filter. Now - empty-`call_id` function calls are skipped (no dangling assistant tool_call) - and any tool result without a matching tool_call id is dropped. (#2893) -- **deps:** remove the `proxifly` npm dependency (#3000 — thanks @terence71-glitch) -- **proxy:** use connection proxy for OAuth refresh (#3012 — thanks @terence71-glitch) -- **usage:** export pure helper functions for unit testing (#3015 — thanks @oyi77) -- **docs/docker:** align memory default docs to 1024MB (#3006 — thanks @terence71-glitch) -- **providers:** fix DuckDuckGo missing API key & update OpenCode free model list (#3008 — thanks @NekoMonci12) -- **claude:** bump Claude Code identity to 2.1.158 and sync beta flags (#3010 — thanks @Tentoxa) -- **test:** increase DB and usage utils coverage to >60% (#3018 — thanks @oyi77) -- **oom:** resolve memory leak in Bottleneck limiter caches and provider registry (#2965 — thanks @soyelmismo) -- **proxy:** show registry provider proxies in dashboard after Custom proxy flow moved them into the proxy registry (#2963 — thanks @terence71-glitch) -- **routing:** add agy to executor map so it uses AntigravityExecutor (#2957 — thanks @ReqX) -- **skills:** avoid Claude assistant tool_result blocks (#2956 — thanks @terence71-glitch) -- **perf:** CPU leak from Bottleneck limiter accumulation + per-request optimizations (#2951 — thanks @soyelmismo) -- **combo:** combo credential resolution ignores target.providerId — prefer combo target's providerId over model-inferred provider (#2946 — thanks @oyi77) -- **dashboard:** v3.8.8 screen fixes — agent-bridge SSR + audit/logs/memory/playground (#2944) -- **claude:** sanitize tool schemas + cloak third-party tool names on native Claude OAuth (#2943 — thanks @NomenAK) -- **auth:** prevent Codex multi-account refresh_token family revocation (#2941) -- **combo:** fix combo vision passthrough and Codex tool history repair (#2940 — thanks @charithharshana) -- **claude:** map WebSearch to Responses web_search (#2938 — thanks @makcimbx) -- **claude:** strip empty Read pages tool input (#2937 — thanks @makcimbx) -- **dashboard:** improve self-service provider quota visibility (#2931 — thanks @guanbear) -- **antigravity:** avoid visible signatureless tool history (#2927 — thanks @dhaern) -- **sse/web-search:** bypass the web-search fallback on a Claude → Claude passthrough so native Claude requests aren't rewritten (#2960 — thanks @terence71-glitch) -- **oom:** prevent per-request memory accumulation (~256MB heap growth) (#2973 — thanks @soyelmismo) -- **perf/proxy:** parallelize provider proxy overlay lookups (#2984 — thanks @terence71-glitch) -- **privacy/PII:** resolve the PII feature flag correctly and fix PII response sanitization in streaming SSE requests (#3021 — thanks @dangeReis) -- **electron:** improve macOS window chrome (#3029 — thanks @bobbyunknown) -- **i18n:** fix missing API key scope translations (#3031 — thanks @guanbear) -- **stream/responses:** drop a leaked chat bootstrap chunk for Responses-API clients (#3035 — thanks @CitrusIce) -- **docker:** warn-only on the `/app/data` permission check instead of `exit 1`, so a non-writable bind mount no longer kills the container at boot (#3036 — thanks @wussh) -- **mcp:** resolve streamable-HTTP transport readiness reporting an offline status (#3037 — thanks @Chewji9875) -- **dashboard:** use a lightweight ping endpoint for the MaintenanceBanner (fixes #3040) (#3043 — thanks @herjarsa) -- **test:** resolve pre-existing test failures — env sync, PII, quota, sidebar (#3039 — thanks @oyi77) -- **docs/mcp:** regenerate the mcp-tools diagram for 43 tools and fix the tool count (#3028 — thanks @diegosouzapw) -- **mcp:** move `enforceScopes` guard before `MCP_TOOL_MAP` lookup, add inline `scopes` parameter to `withScopeEnforcement()`, and declare scopes on all 24 dynamic tool definitions (memory, skills, plugins, gamification, compression) to fix scope enforcement for dynamic MCP tool groups (#2958) +- **codex/服务商:** `POST /api/providers/[id]/refresh`(手动/自动"刷新令牌"端点)不再轮换使用轮换刷新令牌的服务商(Codex/OpenAI 共享一个 Auth0 `client_id`)。这是最后一个未设防的主动刷新入口点: + 当仪表盘在页面加载时自动刷新每个即将过期的连接(或旧缓存前端批量调用该端点), + 每个 Codex 账户的一次性 refresh_token 都会被轮换,Auth0 吊销整个令牌族 (`openai/codex#9648`) — 除最后一个外所有账户都会因 `[403] ` 而失效。该端点现在对轮换服务商跳过主动轮换,并交由响应式、串行化的 401 路径处理(与 `refreshAndUpdateCredentials` 和连接测试路由使用相同的守卫)。 +- **codex/配额:** 打开配额 / 服务商仪表盘不再断开 Codex 多账户设置。配额同步路径 + (`refreshAndUpdateCredentials`) 曾主动刷新每个连接 — 对于 + 轮换刷新令牌的服务商(Codex/OpenAI 共享一个 Auth0 `client_id`),它 + 并发刷新了兄弟账户,导致 Auth0 吊销整个令牌族 + (`openai/codex#9648`),除最后一个外所有账户都因 + `[403] ` 而失效。配额路径现在对轮换服务商跳过主动刷新 + (`rotationGroupFor`),并复用当前的 access_token, + 将真正的过期交由响应式、串行化的 401 路径处理。纵深防御: + `serializeRefresh` 现在在两个 _排队_ 的兄弟刷新之间留有冷却间隔 + (默认 2000 毫秒,可通过 `CODEX_REFRESH_SPACING_MS` 调整,设为 `"0"` 可 + 退出),同时立即释放单个刷新,因此响应式路径不会增加延迟。 +- **负载规则:** 保存的负载规则现在在服务器重启后依然有效。当没有 + 内存中的覆盖设置时(启动钩子运行前的新进程,或 + 独立构建中的单独模块实例),`getPayloadRulesConfig` + 现在在读取文件配置之前先读取数据库持久化的规则(真实数据源), + 而不是静默返回空的文件默认值。(#2986) +- **模型/自定义:** 自定义模型现在可以携带每个模型的 `targetFormat` + 覆盖(例如,一个 opencode-go 自定义模型必须使用 Anthropic Messages + 格式)。此前自定义模型始终按 OpenAI 兼容格式路由,因为 + `targetFormat` 既未持久化,也未在路由时查询。已贯穿 + `addCustomModel`/`replaceCustomModels`/`updateCustomModel`、API + Schema/路由、`getModelInfo` 以及 chatCore 的 targetFormat 解析。(#2905) +- **服务商/pollinations:** 路由至 `gen.pollinations.ai/v1` 而非已 + 废弃的 `text.pollinations.ai` 主机,后者现在对所有模型返回 `404 "legacy API"`。 + gen 网关是当前的 OpenAI 兼容端点。(#2987) +- **执行器/codex:** 对免费计划 Codex 账户 (`workspacePlanType === "free"`) 移除 CLI 注入的 `image_generation` 托管工具, + 这些账户无法在服务端运行该工具,否则会收到上游 400 错误。付费计划保留该工具。 + (镜像 CLIProxyAPI 的免费计划守卫;从 #2980 分析中分离出来) +- **仪表盘:** 自定义服务商(`openai-compatible-*` / `anthropic-compatible-*`) + 现在在活跃请求面板、代理日志和首页服务商拓扑中显示用户指定的节点名称,而非原始 UUID id。 + 显示标签解析器已提取为共享工具,供所有界面复用 + (此前只有请求日志查看器解析了该标签)。(#2968) +- **docker:** 独立启动器(Docker `CMD`)现在遵循 + `OMNIROUTE_MEMORY_MB`(默认 512,范围 [64, 16384])并覆盖 + 镜像 `NODE_OPTIONS` 回退值,修复了高负载 / 大型 SQLite 数据库下的随机 OOM 崩溃。 +- **docker:** 添加 `web` compose 配置文件(`omniroute-web`,目标 `runner-web`, + 镜像 `omniroute:web`),使 web-cookie 服务商(gemini-web、claude-web、 + claude-turnstile)开箱即用 — 默认 `base` 镜像不包含 + Chromium/Playwright,导致这些服务商失败并报错 + "Executable doesn't exist at .../ms-playwright/chromium..."。(#2832) +- **路由/codex:** 修复两个 gpt-5.5 Codex 缺陷 (#2877)。(A) 对于仅有 Codex + 的账户,裸 `gpt-5.5` Responses 请求被重新路由到 codex,但模型 + 被硬编码为 `gpt-5.5-medium`(`chatHelpers.ts`);执行器将该 + `-medium` 后缀解读为显式 `modelEffort`,根据 #2331 覆盖了 + 客户端的 `reasoning.effort=xhigh`,静默降级 — 现在保留裸 + `gpt-5.5` id,使客户端 effort 生效。(B) `gpt-5.5-xhigh`/`-high`/`-low` + 被错误路���到 `openai`(→ 对仅有 codex 的用户显示"No credentials");带后缀的 + 变体现在已加入 `CODEX_PREFERRED_UNPREFIXED_MODELS`,使其推断为 codex。 +- **sse/chatCore:** 移除 `handleChatCore` 中重复的 `const settings` 声明 + (随每密钥流默认模式功能一同引入)。同一作用域内的重复声明导致 esbuild/tsx 失败, + 报错"The symbol 'settings' has already been declared",使所有 + 导入 chatCore 的单元测试变红并破坏了生产构建。现在复用之前 + 合并的 `settings` 常量。 +- **db/数据库迁移:** 解决 `077` 迁移版本冲突 + (`077_api_key_stream_default_mode.sql` vs `077_quota_pools.sql`),该冲突导致 + `getMigrationFiles()` 抛出异常并在启动时阻塞 `getDbInstance()`(应用无法 + 启动;所有涉及数据库的测试均失败)。将无依赖、 + 幂等的 `quota_pools` 迁移重新编号为 `085`,将非幂等的 + `api_key_stream_default_mode` `ALTER` 保留在 `077`,添加了回溯性的 + `isSchemaAlreadyApplied` 守卫(case `085`),以及一个强制唯一迁移前缀的回归测试。 +- **路由/推理重放:** OpenCode `big-pickle`(服务商 `opencode`/`oc` + 和 `opencode-zen`)现在通过新的 `RegistryModel.interleavedField` 字段声明交错 `reasoning_content` 契约, + 使后续/工具使用轮次能够重放 reasoning_content。此前 `big-pickle` 未匹配任何重放模式, + 失败并报错 `[400] The reasoning_content in the thinking mode must be passed +back to the API`(其 DeepSeek-thinking 上游无法从模型 id 检测到, + 且 `requiresReasoningReplay` 不消费 `supportsReasoning`)。 + `getResolvedModelCapabilities` 现在会暴露注册表中的 `interleavedField`。(#2900) +- **服务商/github-copilot:** 内置的 GitHub Copilot Claude Opus 和 Gemini + 模型(`claude-opus-4.7`、`claude-opus-4-5-20251101`、`gemini-3.1-pro-preview`、 + `gemini-3-flash-preview`)不再携带 `targetFormat: "openai-responses"`,因此 + 它们通过 `chat/completions` 路由(服务商默认值,与正常工作的 + `claude-opus-4.6` 相同),而非 Responses API,因为 Copilot 不为 + 非 OpenAI 模型提供 Responses API(会返回 `[400]`)。原生 OpenAI `gpt-*` 模型保留 + Responses API。(#2911) +- **翻译器/responses:** Codex Desktop 将 `image_generation` 托管 + 工具注入到每个 Responses API 请求中(即使是纯文本请求),OmniRoute + 曾拒绝该请求并报错 `[400] image_generation tool type is not supported`。现在 + 将其视为 `tool_search`:允许通过工具类型校验器,并在转发到 Chat Completions 之前 + 从工具数组中静默移除。(#2950) +- **combo/构建器:** 无需认证的 OpenCode Free combo 条目现在使用 `oc/` 路由 + 别名,而非 `opencode/` 前缀。`parseModel("opencode/")` + 解析到 `opencode-zen` API 密钥层级(通过手动 `ALIAS_TO_PROVIDER_ID` + 覆盖),因此使用裸服务商 id 构建的 combo 会被错误路由,偏离 + 无需认证的 `opencode` 服务商;`oc/` 解析正确。(#2901) +- **容灾/服务商:** 路由限制 `403`(例如 Fireworks Fire Pass + `fpk_*` 密钥在 `/models` 上返回"…not authorized for this route.",而 + 聊天仍然正常)不再将连接标记为不可用。服务商 + 校验对于此类 403 会回退到聊天探测,而不是返回 + "Invalid API key",且 `checkFallbackError` 将它们短路为无冷却。 + 真正的认证失败(401 / 通用 403)仍然快速失败。(#2929) +- **认证/opencode-zen:** OpenCode Zen 免费模型现在可以在 Playground + 和 combo 中无需 API 密钥即可使用。`opencode-zen` 提供公开、无需注册的 + 端点 (`https://opencode.ai/zen/v1`);当未配置 API 密钥连接时, + 凭证解析现在回退到匿名(无需认证)访问, + 而不是失败并报错"No credentials for provider: opencode-zen"。配置的 + 活跃密钥在存在时仍然会被使用。(#2962) +- **翻译器/responses:** 修复了上游 `[400] Messages with role 'tool' +must be a response to a preceding message with 'tool_calls'` 错误,当 Codex + 客户端发送带有空/缺失 `call_id` 的 `function_call` 时触发。孤立的 + `function_call_output` 此前绕过了孤立过滤器。现在 + 空 `call_id` 函数调用会被跳过(不产生悬空的助手 tool_call), + 且任何没有匹配 tool_call id 的工具结果都会被丢弃。(#2893) +- **deps:** 移除 `proxifly` npm 依赖 (#3000 — 感谢 @terence71-glitch) +- **proxy:** OAuth 刷新时使用连接代理 (#3012 — 感谢 @terence71-glitch) +- **usage:** 导出纯辅助函数供单元测试使用 (#3015 — 感谢 @oyi77) +- **docs/docker:** 将内存默认文档对齐至 1024MB (#3006 — 感谢 @terence71-glitch) +- **providers:** 修复 DuckDuckGo 缺少 API key 并更新 OpenCode 免费模型列表 (#3008 — 感谢 @NekoMonci12) +- **claude:** 将 Claude Code 身份升级到 2.1.158 并同步 beta 标志 (#3010 — 感谢 @Tentoxa) +- **test:** 提升 DB 和 usage 工具覆盖��达 60% 以上 (#3018 — 感谢 @oyi77) +- **oom:** 解决 Bottleneck 限流器缓存和服务商注册表中的内存泄漏 (#2965 — 感谢 @soyelmismo) +- **proxy:** 自定义代理流程迁移后在控制台显示注册表服务商代理 (#2963 — 感谢 @terence71-glitch) +- **routing:** 将 agy 添加到 executor 映射以使用 AntigravityExecutor (#2957 — 感谢 @ReqX) +- **skills:** 避免 Claude 助手 tool_result 阻塞 (#2956 — 感谢 @terence71-glitch) +- **perf:** Bottleneck 限流器累积导致 CPU 泄漏及每请求优化 (#2951 — 感谢 @soyelmismo) +- **combo:** combo 凭证解析忽略 target.providerId — 优先使用 Combo 目标的 providerId 而非模型推断的服务商 (#2946 — 感谢 @oyi77) +- **dashboard:** v3.8.8 界面修复 — agent-bridge SSR + 审计/日志/记忆/工作室 (#2944) +- **claude:** 对原生 Claude OAuth 清洗工具 schema 并隐藏第三方工具名称 (#2943 — 感谢 @NomenAK) +- **auth:** 防止 Codex 多账户 refresh_token 家族吊销 (#2941) +- **combo:** 修复 combo vision 透传和 Codex 工具历史修复 (#2940 — 感谢 @charithharshana) +- **claude:** 将 WebSearch 映射�� Responses web_search (#2938 — 感谢 @makcimbx) +- **claude:** 去除空的 Read pages 工具输入 (#2937 — 感谢 @makcimbx) +- **dashboard:** 改进自助服务商配额可见性 (#2931 — 感谢 @guanbear) +- **antigravity:** 避免可见的无签名工具历史 (#2927 — 感谢 @dhaern) +- **sse/web-search:** Claude → Claude 透传时绕过 web-search 容灾,使原生 Claude 请求不被重写 (#2960 — 感谢 @terence71-glitch) +- **oom:** 防止每请求内存累积(约 256MB 堆增长)(#2973 — 感谢 @soyelmismo) +- **perf/proxy:** 并行化服务商代理覆盖查找 (#2984 — 感谢 @terence71-glitch) +- **privacy/PII:** 正确解析 PII 特性标志并修复流式 SSE 请求中的 PII 响应清洗 (#3021 — 感谢 @dangeReis) +- **electron:** 改进 macOS 窗口外观 (#3029 — 感谢 @bobbyunknown) +- **i18n:** 修复缺失的 API key 作用域翻译 (#3031 — 感谢 @guanbear) +- **stream/responses:** 对 Responses-API 客户端丢弃泄漏的聊天启动块 (#3035 — 感谢 @CitrusIce) +- **docker:** `/app/data` 权限检查仅警告而非 `exit 1`,使不可写挂载不再导致容器启动失败 (#3036 — 感谢 @wussh) +- **mcp:** 解决 streamable-HTTP 传输就绪状态报告为离线的问题 (#3037 — 感谢 @Chewji9875) +- **dashboard:** 使用轻量级 ping 端点作为维护横幅(修复 #3040)(#3043 — 感谢 @herjarsa) +- **test:** 解决已有测试失败 — env 同步、PII、配额、侧边栏 (#3039 — 感谢 @oyi77) +- **docs/mcp:** 为 43 个工具重新生成 mcp-tools 图表并修复工具计数 (#3028 — 感谢 @diegosouzapw) +- **mcp:** 将 `enforceScopes` 守卫移到 `MCP_TOOL_MAP` 查找之前,为 `withScopeEnforcement()` 添加内联 `scopes` 参数,并为所有 24 个动态工具定义(memory、skills、plugins、gamification、compression)声明作用域,修复动态 MCP 工具组的作用域执行 (#2958) --- ## [3.8.7] — 2026-05-29 -### ✨ New Features +### ✨ 新功能 -- **api (self-service):** add `GET /api/v1/me/status` so a delegated API key can view its own usage (USD used, budget percent, token totals) and optional shared Codex account quota, backed by migration `075_api_key_self_service_usage_scopes` (#2908 — thanks @guanbear). -- **analytics:** roll up usage logs to `daily_usage_summary` before raw log cleanup, and query a SQL `UNION` of raw and rolled-up data to prevent analytics history data loss (#2904 — thanks @unitythemaker). -- **perf (RAM):** reduce server memory footprint by capping 11 in-memory caches, limiting SQLite page cache, lazy-loading provider registries via Proxy, and optimizing Next.js startup database probes (#2903 — thanks @soyelmismo). +- **api (自助服务):** 新增 `GET /api/v1/me/status`,使委托 API key 可查看自身用量(已用 USD、预算百分比、token 总数)及可选的共享 Codex 账户配额,由迁移 `075_api_key_self_service_usage_scopes` 支持 (#2908 — 感谢 @guanbear)。 +- **analytics:** 在原始日志清理前将用量日志汇总到 `daily_usage_summary`,并通过 SQL `UNION` 查询原始和汇总数据,防止分析历史数据丢失 (#2904 — 感谢 @unitythemaker)。 +- **perf (内存):** 通过限制 11 个内存缓存、限制 SQLite 页面缓存、通过 Proxy 延迟加载服务商注册表以及优化 Next.js 启动时的数据库探测来降低服务器内存占用 (#2903 — 感谢 @soyelmismo)。 -### 🔧 Bug Fixes +### 🔧 问题修复 -- **token-accounting:** prefer `prompt_tokens` over compatibility `input_tokens` for Anthropic Claude streams to avoid double-counting cached tokens (#2904 — thanks @unitythemaker). +- **token-accounting:** 对于 Anthropic Claude 流式传输,优先使用 `prompt_tokens` 而非兼容的 `input_tokens`,避免重复计算缓存 token (#2904 — 感谢 @unitythemaker)。 -- **agy:** add the **Antigravity CLI (`agy`)** as a standalone OAuth provider next to `gemini-cli`/`antigravity`. It reuses the antigravity inference backend (identical Google client, `daily-cloudcode-pa.googleapis.com`) but ships its own model catalog — notably the Claude models the backend exposes (`claude-opus-4-6-thinking`, `claude-sonnet-4-6`) — its own account pool, and connection methods: import the `agy` CLI token file (paste/upload), auto-detect a local CLI login (`~/.gemini/antigravity-cli/antigravity-oauth-token`), browser OAuth, and bulk/ZIP import. New routes: `POST /api/providers/agy-auth/{import,import-bulk,zip-extract,apply-local}`. +- **agy:** 将 **Antigravity CLI (`agy`)** 作为独立的 OAuth 服务商,与 `gemini-cli`/`antigravity` 并列。它复用 antigravity 推理后端(相同的 Google client、`daily-cloudcode-pa.googleapis.com`),但提��自己的模型目录——特别是后端暴露的 Claude 模型(`claude-opus-4-6-thinking`、`claude-sonnet-4-6`)——以及自己的账户池和连接方法:导入 `agy` CLI token 文件(粘贴/上传)、��动检测本地 CLI 登录(`~/.gemini/antigravity-cli/antigravity-oauth-token`)、浏览器 OAuth 和批量/ZIP 导入。新路由:`POST /api/服务商/agy-认证/{import,import-bulk,zip-extract,apply-local}`。 -### Breaking Changes +### 破坏性变更 -- **proxy-logs:** `GET /api/usage/proxy-logs` now returns `clientIp` instead of `publicIp` for each log entry. External consumers reading `log.publicIp` must update to `log.clientIp`. The underlying SQLite column (`public_ip`) is unchanged, so callers that query the database directly are unaffected (#2880 — thanks @rdself). +- **proxy-logs:** `GET /api/usage/proxy-logs` 现在对每条日志返回 `clientIp` 而非 `publicIp`。读取 `log.publicIp` 的外部消费者必须更新为 `log.clientIp`。底层 SQLite 列(`public_ip`)保持不变,因此直接查询数据库的调用者不受影响 (#2880 — 感谢 @rdself)。 -### Known Inconsistency +### 已知不一致 -- **log-export:** `GET /api/logs/export?type=proxy-logs` returns raw SQLite rows whose IP field is still named `public_ip` (the historical column name). This differs from the `clientIp` field exposed by `GET /api/usage/proxy-logs`. The two endpoints are intentionally inconsistent for now and will be aligned in a future migration (#2880). +- **log-export:** `GET /api/logs/export?type=proxy-logs` 返回原始 SQLite 行,其 IP 字段仍名为 `public_ip`(历史列名)。这与 `GET /api/usage/proxy-logs` 暴露的 `clientIp` 字段不同。这两个端点目前故意不一致,将在未来的迁移中对齐 (#2880)。 -### ✨ New Features +### ✨ 新功能 -- **usage:** add per-API-key token limits scoped to model/provider/global with two-tier inline enforcement and in-memory cache accelerator (#2888 — thanks @mugnimaestra). -- **providers:** audit web cookie providers, fix 4 missing registry entries, and add DuckDuckGo AI Chat provider (#2862 — thanks @oyi77). -- **compression:** expand pt-BR pack with 34 new rules inspired by the troglodita project (#2818 — thanks @leninejunior). +- **usage:** 新增按 API key 的 token 限制,可按模型/服务商/全局设定,支持两级内联执行和内存缓存加速 (#2888 — 感谢 @mugnimaestra)。 +- **providers:** 审查 web cookie 服务商,修复 4 个缺失的注册表条目,并新增 DuckDuckGo AI Chat 服务商 (#2862 — 感谢 @oyi77)。 +- **compression:** 扩展 pt-BR 语言包,新增 34 条受 troglodita 项目启发的规则 (#2818 — 感谢 @leninejunior)。 -### 🔧 Bug Fixes +### 🔧 问题修复 -- **oauth:** hotfix Windsurf login — drop dead PKCE flow, promote import-token, and resolve SQLite bind type errors (#2884 — thanks @yunaamelia). -- **models:** prune stale synced available models for inactive connections and dynamically map Antigravity MITM aliases loop-safely (#2886 — thanks @herjarsa). -- **antigravity:** harden signatureless tool history replay by making text representation inert (#2878 — thanks @dhaern). -- **i18n:** complete 144 missing Portuguese (pt-BR) locale keys and synchronize them with English (#2870 — thanks @alltomatos). -- **opencode-go:** add OpenCode Go provider limits quota fetcher to retrieve Z.AI quota windows (#2861 — thanks @RajvardhanPatil07). -- **reasoning:** gate reasoning trace replay injection on model interleaved capability metadata (#2843 — thanks @nickwizard). -- **audio:** construct multipart body manually for transcription form-data to prevent dropped boundary headers under Next.js fetch (#2842 — thanks @soyelmismo). -- **gemini-cli:** prefer real Google Cloud project IDs over default-project during model synchronization (#2841 — thanks @nickwizard). -- **mcp:** redirect console.log and console.warn startup messages to stderr in stdio MCP mode to prevent JSON-RPC parsing failures (#2840 — thanks @disonjer). -- **antigravity:** normalize unescaped tool calls and classify resource exhaustion 429 errors as lockout cooldowns (#2828 — thanks @Ardem2025). -- **sse:** repair RTK engine defaults to resolve consecutive-line deduplication and direct compression calls (#2825 — thanks @leninejunior). -- **fix(usage):** add opencode-go / opencode / opencode-zen quota fetcher so the provider limits page surfaces $12/5h, $30/wk, $60/mo windows alongside other quota-aware providers ([#2852](https://github.com/diegosouzapw/OmniRoute/issues/2852) — thanks @apoapostolov) +- **oauth:** 紧急修复 Windsurf 登录——移除已失效的 PKCE 流程、提升 import-token 方式,并解决 SQLite 绑定类型错误 (#2884 — 感谢 @yunaamelia)。 +- **models:** 清理非活跃连接中过期的同步可用模型,并安全地动态映射 Antigravity MITM 别名,避免循环引用 (#2886 — 感谢 @herjarsa)。 +- **antigravity:** 通过将文本表示设为惰性来加固无签名的工具历史重放 (#2878 — 感谢 @dhaern)。 +- **i18n:** 补全 144 个缺失的葡萄牙语 (pt-BR) 语言包 key 并与英语同步 (#2870 — 感谢 @alltomatos)。 +- **opencode-go:** 为 OpenCode Go 添加服务商限制配额获取器,以检索 Z.AI 配额窗口 (#2861 — 感谢 @RajvardhanPatil07)。 +- **reasoning:** 基于模型的交错能力元数据控制推理追踪重放注入 (#2843 — 感谢 @nickwizard)。 +- **audio:** 为转录 form-data 手动构建 multipart body,防止 Next.js fetch 下丢失边界头 (#2842 — 感谢 @soyelmismo)。 +- **gemini-cli:** 在模型同步期间优先使用真实的 Google Cloud 项目 ID 而非 default-project (#2841 — 感谢 @nickwizard)。 +- **mcp:** 在 stdio MCP 模式下将启动时的 console.log 和 console.warn 消息重定向到 stderr,防止 JSON-RPC 解析失败 (#2840 — 感谢 @disonjer)。 +- **antigravity:** 规范化未转义的 tool call 并将资源耗尽 429 错误归类为锁定冷却 (#2828 — 感谢 @Ardem2025)。 +- **sse:** 修复 RTK 引擎默认值,解决连续行去重和直接压缩调用的问题 (#2825 — 感谢 @leninejunior)。 +- **fix(usage):** 添加 opencode-go / opencode / opencode-zen 配额获取器,使服务商限制页面能显示 $12/5h、$30/wk、$60/mo 时间窗口及其他配额感知型服务商 ([#2852](https://github.com/diegosouzapw/OmniRoute/issues/2852) — 感谢 @apoapostolov) --- ## [3.8.6] — 2026-05-27 -### 🧹 Chores +### 🧹 维护 -- **gitignore:** ignore `.claude/settings.local.json` so per-user Claude Code permissions never get committed by accident -- **release:** version bump and metadata sync (package.json, package-lock.json, electron, open-sse, openapi.yaml) +- **gitignore:** 忽略 `.claude/settings.local.json`,防止每个用户的 Claude Code 权限被意外提交 +- **release:** 版本号提升和元数据同步 (package.json, package-lock.json, electron, open-sse, openapi.yaml) -> v3.8.6 is a maintenance/scaffolding patch. All feature and bug-fix work from the post-v3.8.5 cycle (44 commits — community PRs #2777, #2782–#2787, #2789, #2790, plus internal hotfixes) was already integrated into v3.8.5 and is documented under that section. +> v3.8.6 是一个维护/脚手架补丁。v3.8.5 之后周期的所有功能和问题修复工作(44 次提交——社区 PR #2777、#2782–#2787、#2789、#2790 以及内部紧急修复)已全部合并到 v3.8.5 中,并在该版本章节中记录。 --- ## [3.8.5] — 2026-05-26 -### 🔒 Security +### 🔒 安全 -- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated (#2712) +- **authz:** 未认证时将 `/home` 和 `/home/:path*` 重定向到 `/login` (#2712) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 (#2650) -- **deepseek:** guard PoW solver Web Worker handler under Node strict mode (#2724) -- **combos:** include no-auth providers in the combo builder picker (#2737) -- **translator:** allow the `web_search` server-tool family in the Responses API translator (#2695) -- **oauth:** register the missing `trae` provider with `import_token` flow (#2658) -- **model:** merge settings-based aliases with the legacy DB alias namespace (#2618, #2208) -- **kiro:** clipboard fallback for HTTP / non-secure contexts (#2689) -- **cli:** raise `omniroute serve` ready timeout to 60s with TCP fallback for Windows cold start (#2460) +- **mcp:** 打破 callLogs ↔ compliance 的 ESM 循环依赖,该循环导致打包后的 MCP 服务器在 Node.js 24 上死锁 (#2650) +- **deepseek:** 在 Node 严格模式下保护 PoW 求解器 Web Worker 处理器 (#2724) +- **combos:** 在 combo 构建器选择器中包含免认证服务商 (#2737) +- **translator:** 在 Responses API 翻译器中允许 `web_search` 服务端工具族 (#2695) +- **oauth:** 注册缺失的 `trae` 服务商,使用 `import_token` 流程 (#2658) +- **model:** 将基于设置的别名与旧版 DB 别名命名空间合并 (#2618, #2208) +- **kiro:** HTTP / 非安全上下文的剪贴板回退 (#2689) +- **cli:** 将 `omniroute serve` 就绪超时提升到 60s,并为 Windows 冷启动添加 TCP 回退 (#2460) --- @@ -1981,15 +1982,15 @@ must be a response to a preceding message with 'tool_calls'` when a Codex --- -### ✨ New Features +### ✨ 新功能 -### 🔧 Bug Fixes +### 🔧 问题修复 --- ## [3.8.4] — 2026-05-25 -### Added +### 新增 - Embedded services (work in progress — 9Router, CLIProxyAPI; see T-15 for full entry). @@ -1997,40 +1998,40 @@ must be a response to a preceding message with 'tool_calls'` when a Codex ## [3.8.3] — 2026-05-24 -### ✨ New Features +### ✨ 新功能 -- **feat(combos):** universal context handoff for cross-model conversation continuity — structured XML summary system (``) that preserves conversation continuity and handles state transfer when combo routing switches models. ([#2653](https://github.com/diegosouzapw/OmniRoute/pull/2653) — thanks @herjarsa) -- **feat(docs):** migrate `/docs` to Fumadocs MDX with nested routes — replaces the custom docs engine with Fumadocs, adding `[...slug]` catch-all routing, search API at `/docs/api/search`, `source.config.ts` content configuration, and `meta.json` navigation files across 8 doc sections (`architecture/`, `compression/`, `frameworks/`, `guides/`, `ops/`, `reference/`, `routing/`, `security/`). Includes 50+ URL redirects for backward compatibility via `next.config.mjs`. ([#2614](https://github.com/diegosouzapw/OmniRoute/pull/2614) — thanks @ovehbe) -- **feat(dashboard):** add search and filters to `/dashboard/api-manager` — filter bar with search by name/key, active-only toggle (persisted to localStorage), status filter (active/disabled/banned/expired), type filter (standard/manage/restricted), filter count badges, and empty state with "Clear Filters" button. ([#2628](https://github.com/diegosouzapw/OmniRoute/pull/2628) / [#2641](https://github.com/diegosouzapw/OmniRoute/pull/2641) — thanks @diegosouzapw) -- **feat(dashboard):** free-tier grouping with symbolic link in `/dashboard/providers` — groups and displays all free-tier providers from all categories dynamically using `hasFree: true` properties without removing them from their native lists. Displays category dot and amber dot with localizable tooltips, dedupes search results by provider ID, and corrects free tier count statistics. ([#2632](https://github.com/diegosouzapw/OmniRoute/pull/2632) — thanks @diegosouzapw) -- **feat(dashboard):** risk notice modal for sensitive providers — show a soft, informative warning modal when connecting to session-based or OAuth providers (like Claude, Cursor, Copilot) for the first time. Adds `subscriptionRisk` properties to 20 providers, localizable templates, and stores acknowledgment in localStorage. ([#2633](https://github.com/diegosouzapw/OmniRoute/pull/2633) / [#2638](https://github.com/diegosouzapw/OmniRoute/pull/2638) — thanks @diegosouzapw) -- **feat(dashboard):** refactor free-tier provider dashboard layout — cleans up visual clutter, reorganizes categories, hides redundant banners, and integrates free-tier categories nicely into the primary provider interface. ([#2640](https://github.com/diegosouzapw/OmniRoute/pull/2640) — thanks @diegosouzapw) -- **feat(dashboard):** mini-playground inline (Phase 4) — integrated interactive mini-playground capabilities to provider details pages, including support for specialized example cards (Embedding, Image, LLM Chat, Music, STT, TTS, Video, Web Fetch, Web Search), unified API key loading hooks, model listing hooks, and curl command builder. ([#2648](https://github.com/diegosouzapw/OmniRoute/pull/2648) — thanks @diegosouzapw) -- **feat(webfetch):** category support with dedicated media providers page and executors for Firecrawl, Jina Reader, and Tavily. ([#2645](https://github.com/diegosouzapw/OmniRoute/pull/2645) — thanks @diegosouzapw) -- **feat(adapta):** integrate Adapta Org (`adapta-web`) provider with automatic Clerk authentication refresh and custom onboarding tutorial modal. ([#2643](https://github.com/diegosouzapw/OmniRoute/pull/2643) — thanks @df4p) -- **feat(i18n):** complete translations for Simplified Chinese — translates 1220 missing keys bringing UI coverage to 98.8% with 0 placeholders. ([#2655](https://github.com/diegosouzapw/OmniRoute/pull/2655) — thanks @L-aros) +- **feat(combos):** 通用上下文交接,实现跨模型对话连续性 — 结构化 XML 摘要系统 (``),在 Combo 路由切换模型时保持对话连续性并处理状态转移。([#2653](https://github.com/diegosouzapw/OmniRoute/pull/2653) — 感谢 @herjarsa) +- **feat(docs):** 将 `/docs` 迁移到 Fumadocs MDX,支持嵌套路由 — 用 Fumadocs 替换自定义文档引擎,新增 `[...slug]` 通配路由、`/docs/api/搜索` 搜索 API、`source.配置.ts` 内容配置,以及 8 个文档分区的 `meta.json` 导航文件(`architecture/`、`压缩/`、`frameworks/`、`guides/`、`ops/`、`reference/`、`路由/`、`security/`)。包含 50+ 个 URL 重定向以确保向后兼容。([#2614](https://github.com/diegosouzapw/OmniRoute/pull/2614) — 感谢 @ovehbe) +- **feat(dashboard):** 为 `/dashboard/api-manager` 添加搜索和过滤功能 — 过滤栏支持按名称/key 搜索、仅活跃开关(持久化到 localStorage)、状态过滤(活跃/禁用/封禁/过期)、类型过滤(标准/管理/受限)、过滤计数徽章,以及带 "清除过滤" 按钮的空状态。([#2628](https://github.com/diegosouzapw/OmniRoute/pull/2628) / [#2641](https://github.com/diegosouzapw/OmniRoute/pull/2641) — 感谢 @diegosouzapw) +- **feat(dashboard):** `/dashboard/providers` 中的免费层分组及符号链接 — 使用 `hasFree: true` 属性动态分组显示所有类别的免费层服务商,不将其从原生列表中移除。显示类别圆点和琥珀色圆点及可本地化提示,按服务商 ID 去重搜索结果,修正免费层计数统计。([#2632](https://github.com/diegosouzapw/OmniRoute/pull/2632) — 感谢 @diegosouzapw) +- **feat(dashboard):** 敏感服务商风险提示弹窗 — 首次连接到基于会话或 OAuth 的服务商(如 Claude、Cursor、Copilot)时显示温和的信息警告弹窗。为 20 个服务商添加 `subscriptionRisk` 属性、可本地化模板,并将确认信息存储到 localStorage。([#2633](https://github.com/diegosouzapw/OmniRoute/pull/2633) / [#2638](https://github.com/diegosouzapw/OmniRoute/pull/2638) — 感谢 @diegosouzapw) +- **feat(dashboard):** 重构免费层服务商仪表盘布局 — 清理视觉杂乱、重新组织类别、隐藏冗余横幅,将免费层类别优雅集成到主服务商界面。([#2640](https://github.com/diegosouzapw/OmniRoute/pull/2640) — 感谢 @diegosouzapw) +- **feat(dashboard):** 内联迷你工作室(第四阶段)— 在服务商详情页集成交互式迷你工作室功能,包括专业示例卡片(Embedding、Image、LLM Chat、Music、STT、TTS、Video、Web Fetch、Web Search)、统一的 API Key 加载钩子、模型列表钩子和 curl 命令构建器。([#2648](https://github.com/diegosouzapw/OmniRoute/pull/2648) — 感谢 @diegosouzapw) +- **feat(webfetch):** 分类支持,含专用媒体服务商页面和 Firecrawl、Jina Reader、Tavily 的执行器。([#2645](https://github.com/diegosouzapw/OmniRoute/pull/2645) — 感谢 @diegosouzapw) +- **feat(adapta):** 集成 Adapta Org (`adapta-web`) 服务商,支持自动 Clerk 认证刷新和自定义引导教程弹窗。([#2643](https://github.com/diegosouzapw/OmniRoute/pull/2643) — 感谢 @df4p) +- **feat(i18n):** 完成简体中文翻译 — 翻译 1220 个缺失 key,UI 覆盖率达 98.8%,零占位符。([#2655](https://github.com/diegosouzapw/OmniRoute/pull/2655) — 感谢 @L-aros) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **fix(settings):** Require Login modal Cancel button text and dismissal — modal now renders localized cancel label via the `common` namespace and closes correctly without modifying settings when cancelled. ([#2649](https://github.com/diegosouzapw/OmniRoute/pull/2649) — thanks @Chewji9875) -- **fix(deepseek-web):** re-apply SSE parser, prompt format, and error handling fixes — handles all 3 DeepSeek SSE stream formats (initial fragments, APPEND operations, bare string tokens), uses non-greedy regex for markdown image stripping, simplifies prompt to single-turn, checks `json.code` before token extraction, and uses `accessToken` fallback for session cache eviction on auth errors. ([#2616](https://github.com/diegosouzapw/OmniRoute/pull/2616) — thanks @ovehbe) -- **fix(deepseek-web):** SSE thinking/search routing and session lifecycle — properly routes thinking vs content fragments based on `thinking_enabled` flag, handles search results with citation indices, appends search result footnotes, refactors `transformSSE()` and `collectSSEContent()` with shared helpers. ([#2624](https://github.com/diegosouzapw/OmniRoute/pull/2624) — thanks @ovehbe) -- **fix(codex):** use allowlist to strip non-Responses-API fields in non-passthrough path — strips residual Chat Completions fields (`stream_options`, `service_tier`, `store`, `metadata`) from the request body when routing through the non-passthrough (translation) code path, preventing GPT-5.5 from receiving invalid parameters. ([#2615](https://github.com/diegosouzapw/OmniRoute/pull/2615) — thanks @diegosouzapw) -- **fix(catalog):** skip static PROVIDER_MODELS when synced models exist — prevents stale/duplicate model entries in `/v1/models` for auto-synced providers. ([#2625](https://github.com/diegosouzapw/OmniRoute/pull/2625) — thanks @herjarsa) -- **fix(qoder):** Cosy auth fallback for PAT tokens + vision support for qwen3-vl-plus — when a PAT token gets 401, falls back to Cosy auth against `api1.qoder.sh`; adds `supportsVision: true` to qwen3-vl-plus. ([#2629](https://github.com/diegosouzapw/OmniRoute/pull/2629) — thanks @herjarsa) -- **fix(cli):** register tsx loader and add opencode config subcommand — registers `tsx/esm` at CLI startup so dynamic `.ts` imports resolve; adds `omniroute config opencode` convenience alias. ([#2631](https://github.com/diegosouzapw/OmniRoute/pull/2631) — thanks @amogus22877769) -- **fix(claude):** improve Pi and OpenCode compatibility — adds Pi Coding Agent anchors to system transform removal, stores `_toolNameMap` as non-enumerable, strips `context_management` when thinking is disabled. ([#2621](https://github.com/diegosouzapw/OmniRoute/pull/2621) — thanks @unitythemaker) -- **fix(passthrough):** restore semantic passthrough system-role-only extraction — reverts full `normalizeClaudeUpstreamMessages()` to lighter `extractSystemRoleMessages()` in CC semantic passthrough paths, preventing document/tool chain corruption. ([#2620](https://github.com/diegosouzapw/OmniRoute/pull/2620) — thanks @Tentoxa) -- **fix(kiro):** stabilize conversationId across prompt compression — captures pre-compression body and uses the original first user message as seed for UUID v5, keeping Kiro's AWS conversation context stable. ([#2630](https://github.com/diegosouzapw/OmniRoute/pull/2630) — thanks @HALDRO) -- **fix(t3-chat-web):** close implementation gaps for t3.chat TanStack Start, tracking of stream_options, and retry configurations — parses TSS Turbo Stream Serialization from `_serverFn/*`, tracks request `combo_strategy` via database migration `062_usage_history_combo_strategy.sql`, and makes batch retry backoffs custom-configurable via environment variables. ([#2634](https://github.com/diegosouzapw/OmniRoute/pull/2634) — thanks @oyi77) -- **fix(reasoning):** extend empty `reasoning_content` injection to prevent tool call loops in Kimi K2 and replay models — injects empty `reasoning_content` field to Kimi models during tool-calling sequences to bypass loop issues. ([#2639](https://github.com/diegosouzapw/OmniRoute/pull/2639) — thanks @herjarsa) -- **fix(cli):** Linux autostart via systemd user service on headless VPS — adds auto-generating systemd user service unit for headless setups on Linux, updating tray configs and system variables allowlist (`LOGNAME` and `XDG_CURRENT_DESKTOP`). ([#2635](https://github.com/diegosouzapw/OmniRoute/pull/2635) — thanks @janeza2) -- **fix(combo):** preserve `` tag in SSE stream output for combos when using `context_cache_protection` to ensure correct context pinning round-trips. ([#2646](https://github.com/diegosouzapw/OmniRoute/pull/2646) — thanks @herjarsa) -- **fix(rtk):** prevent false positives in RTK compression by skipping content-based filter matching for non-shell tool results (e.g. read_file, grep_search). ([#2642](https://github.com/diegosouzapw/OmniRoute/pull/2642) — thanks @HALDRO) -- **fix(translator):** enable Claude extended thinking for Copilot Responses-API requests — handles reasoning budget and translations for Copilot. ([#2647](https://github.com/diegosouzapw/OmniRoute/pull/2647) — thanks @ivan-mezentsev) +- **fix(settings):** Require Login modal Cancel button text and dismissal — modal now renders localized cancel label via the `common` namespace and closes correctly without modifying settings when cancelled. ([#2649](https://github.com/diegosouzapw/OmniRoute/pull/2649) — 感谢 @Chewji9875) +- **fix(deepseek-web):** re-apply SSE parser, prompt format, and error handling fixes — handles all 3 DeepSeek SSE 流 formats (initial fragments, APPEND operations, bare string tokens), uses non-greedy regex for markdown image stripping, simplifies 提示 to single-turn, checks `json.code` before token extraction, and uses `accessToken` 容灾 for 会话 缓存 eviction on 认证 errors. ([#2616](https://github.com/diegosouzapw/OmniRoute/pull/2616) — 感谢 @ovehbe) +- **fix(deepseek-web):** SSE thinking/search routing and session lifecycle — properly routes thinking vs content fragments based on `thinking_enabled` 标志, handles 搜索 results with citation indices, appends 搜索 result footnotes, refactors `transformSSE()` and `collectSSEContent()` with shared helpers. ([#2624](https://github.com/diegosouzapw/OmniRoute/pull/2624) — 感谢 @ovehbe) +- **fix(codex):** use allowlist to strip non-Responses-API fields in non-passthrough path — strips residual Chat Completions fields (`stream_options`, `service_tier`, `store`, `metadata`) from the 请求 body when 路由 through the non-passthrough (translation) code path, preventing GPT-5.5 from receiving invalid parameters. ([#2615](https://github.com/diegosouzapw/OmniRoute/pull/2615) — 感谢 @diegosouzapw) +- **fix(catalog):** skip static PROVIDER_MODELS when synced models exist — prevents stale/duplicate 模型 entries in `/v1/模型` for auto-synced 服务商. ([#2625](https://github.com/diegosouzapw/OmniRoute/pull/2625) — 感谢 @herjarsa) +- **fix(qoder):** Cosy auth fallback for PAT tokens + vision support for qwen3-vl-plus — when a PAT token gets 401, falls back to Cosy 认证 against `api1.qoder.sh`; adds `supportsVision: true` to qwen3-vl-plus. ([#2629](https://github.com/diegosouzapw/OmniRoute/pull/2629) — 感谢 @herjarsa) +- **fix(cli):** register tsx loader and add opencode config subcommand — registers `tsx/esm` at CLI startup so dynamic `.ts` imports resolve; adds `omniroute 配置 opencode` convenience alias. ([#2631](https://github.com/diegosouzapw/OmniRoute/pull/2631) — 感谢 @amogus22877769) +- **fix(claude):** improve Pi and OpenCode compatibility — adds Pi Coding Agent anchors to system transform removal, stores `_toolNameMap` as non-enumerable, strips `context_management` when thinking is disabled. ([#2621](https://github.com/diegosouzapw/OmniRoute/pull/2621) — 感谢 @unitythemaker) +- **fix(passthrough):** restore semantic passthrough system-role-only extraction — reverts full `normalizeClaudeUpstreamMessages()` to lighter `extractSystemRoleMessages()` in CC semantic passthrough paths, preventing document/tool chain corruption. ([#2620](https://github.com/diegosouzapw/OmniRoute/pull/2620) — 感谢 @Tentoxa) +- **fix(kiro):** stabilize conversationId across prompt compression — captures pre-压缩 body and uses the original first 用户消息 as seed for UUID v5, keeping Kiro's AWS conversation context stable. ([#2630](https://github.com/diegosouzapw/OmniRoute/pull/2630) — 感谢 @HALDRO) +- **fix(t3-chat-web):** close implementation gaps for t3.chat TanStack Start, tracking of stream_options, and retry configurations — parses TSS Turbo Stream Serialization from `_serverFn/*`, tracks 请求 `combo_strategy` via 数据库 迁移 `062_usage_history_combo_strategy.sql`, and makes batch 重试 backoffs custom-configurable via environment variables. ([#2634](https://github.com/diegosouzapw/OmniRoute/pull/2634) — 感谢 @oyi77) +- **fix(reasoning):** extend empty `reasoning_content` injection to prevent tool call loops in Kimi K2 and replay models — injects empty `reasoning_content` 字段 to Kimi 模型 during tool-calling sequences to bypass loop issues. ([#2639](https://github.com/diegosouzapw/OmniRoute/pull/2639) — 感谢 @herjarsa) +- **fix(cli):** Linux autostart via systemd user service on headless VPS — adds auto-generating systemd user service unit for headless setups on Linux, updating tray configs and system variables allowlist (`LOGNAME` and `XDG_CURRENT_DESKTOP`). ([#2635](https://github.com/diegosouzapw/OmniRoute/pull/2635) — 感谢 @janeza2) +- **fix(combo):** preserve `` tag in SSE stream output for combos when using `context_cache_protection` to ensure correct context pinning round-trips. ([#2646](https://github.com/diegosouzapw/OmniRoute/pull/2646) — 感谢 @herjarsa) +- **fix(rtk):** prevent false positives in RTK compression by skipping content-based filter matching for non-shell tool results (e.g. read_file, grep_search). ([#2642](https://github.com/diegosouzapw/OmniRoute/pull/2642) — 感谢 @HALDRO) +- **fix(translator):** enable Claude extended thinking for Copilot Responses-API requests — handles reasoning 预算 and translations for Copilot. ([#2647](https://github.com/diegosouzapw/OmniRoute/pull/2647) — 感谢 @ivan-mezentsev) - **fix(tests):** remove duplicate assertion in schema coercion & fix(cli): ignore system vars in env check. (thanks @diegosouzapw) -### 📝 Maintenance +### 📝 维护 - **chore(config):** ignore additional agent workflow command files (`.agents/commands/`). (thanks @diegosouzapw) - **chore(config):** ignore `memory-bank` and Cursor agent rules from tracking. (thanks @ovehbe) @@ -2039,200 +2040,200 @@ must be a response to a preceding message with 'tool_calls'` when a Codex ## [3.8.2] — 2026-05-22 -### ✨ New Features +### ✨ 新功能 -- **feat(@omniroute/opencode-plugin):** upstream-provider suffix in model display name — appends provider label to enriched names (e.g. `Claude Opus 4.7 · Claude` vs `Claude Opus 4.7 · Kiro`) so the OC TUI model picker can differentiate same-id models routed through different upstream connections. Default-on, opt-out via `features.providerTag: false`. ([#2602](https://github.com/diegosouzapw/OmniRoute/pull/2602) — thanks @mrmm) -- **feat(@omniroute/opencode-plugin):** provider-tag becomes a prefix + traffic-light compression emoji — provider label now prepends (`Claude - Claude Opus 4.7`) for better TUI column grouping, with smart abbreviation for long labels (`GitHub Models` → `GHM`). Compression pipelines render intensity as emoji (🟢🟡🟠🔴). ([#2604](https://github.com/diegosouzapw/OmniRoute/pull/2604) — thanks @mrmm) -- **feat(providers):** add 7 free-tier providers (Wave 1) — Arcee AI, InclusionAI, Krutrim, Liquid AI, MonsterAPI, Nomic, and Poolside now available as new API-key providers with provider icons, model specs, and full routing support. ([#2479](https://github.com/diegosouzapw/OmniRoute/pull/2479) — thanks @oyi77) -- **feat(providers):** add Astraflow provider support with global + China endpoints — new provider with dual-region base URLs for global and mainland China access. ([#2486](https://github.com/diegosouzapw/OmniRoute/pull/2486) — thanks @ucloudnb666) -- **feat(providers):** add `claude-web` provider — cookie-based Claude Web chat access without OAuth. ([#2476](https://github.com/diegosouzapw/OmniRoute/pull/2476) — thanks @oyi77) -- **feat(providers):** add 14 free-tier providers (Wave 1b) — 360AI, Baichuan, Baidu, ByteDance/Doubao, IDEO, Kuaishou/Kling, Kunlun/Skywork, SenseTime/SenseNova, Stepfun, Tencent HunYuan, Zhipu GLM, Replicate, RunPod, and Modal with provider icons, model specs, and routing support. ([#2488](https://github.com/diegosouzapw/OmniRoute/pull/2488) — thanks @oyi77) -- **feat(hermes):** add rich multi-role Hermes Agent CLI support — 7 configurable roles (default, delegation, vision, compression, web_extract, skills_hub, approval), per-role model selection with YAML config generation, dashboard card with preview, and home widget integration. ([#2526](https://github.com/diegosouzapw/OmniRoute/pull/2526) — thanks @apoapostolov) -- **feat(cloud-agents):** cloud agents UX overhaul — tabs (tasks/agents/settings), status filters, Material icons, duration formatting, cloud agent credentials and health API endpoints, memory stats endpoint. ([#2516](https://github.com/diegosouzapw/OmniRoute/pull/2516) — thanks @oyi77) -- **feat(authz):** manage-scope API keys may reach `/api/mcp/*` from non-loopback — Route Guard Tiers system (LOCAL_ONLY / ALWAYS_PROTECTED / MANAGEMENT), narrow carve-out for remote MCP access gated by `manage` scope; `/api/cli-tools/runtime/*` stays strict-loopback. Includes dashboard AuthzSection, inventory API, and comprehensive docs. ([#2473](https://github.com/diegosouzapw/OmniRoute/pull/2473) — thanks @mrmm) -- **feat(home):** home page customization for experienced users — pin Provider Quota to home, toggle Quick Start and Provider Topology visibility via Appearance settings. ([#2531](https://github.com/diegosouzapw/OmniRoute/pull/2531) — thanks @apoapostolov) -- **feat(home):** automatic refresh of Provider Quota — configurable interval (60s–600s) with toggle in Appearance settings; auto-refreshes pinned quota on the home page. ([#2532](https://github.com/diegosouzapw/OmniRoute/pull/2532) — thanks @apoapostolov) -- **feat(@omniroute/opencode-plugin):** OmniRoute OpenCode plugin — live models fetched from OmniRoute API, combo-aware model listing, Gemini request sanitization, multi-instance support, auth flow integration, and 10 test files. ([#2529](https://github.com/diegosouzapw/OmniRoute/pull/2529) — thanks @mrmm) -- **feat(executors):** forward OpenCode client headers to upstream providers — OpenCode-specific headers are now forwarded through the executor pipeline for improved compatibility. ([#2538](https://github.com/diegosouzapw/OmniRoute/pull/2538) — thanks @kang-heewon) -- **feat(fireworks):** add new models with `modelIdPrefix` support — generic registry field that stores short model IDs and prepends the full path prefix before upstream API calls. Adds 6 new Fireworks models, `modelsUrl` for dynamic sync, and Qwen3 reranker. ([#2560](https://github.com/diegosouzapw/OmniRoute/pull/2560) — thanks @HALDRO) -- **feat(@omniroute/opencode-plugin):** readable + filterable + offline-resilient model picker — `usableOnly` filter (only show providers with healthy connections), `diskCache` for offline hydration, `Combo:` prefix labeling, and compression metadata tags in combo display names. ([#2572](https://github.com/diegosouzapw/OmniRoute/pull/2572) — thanks @mrmm) -- **feat(smart-pipeline):** multi-stage pipeline for auto combo routing — rule-based + intent-classifier + domain-specific stages with configurable pipeline router, accuracy benchmarks, and comprehensive tests. ([#2551](https://github.com/diegosouzapw/OmniRoute/pull/2551) — thanks @oyi77) -- **feat(ops):** skip DB health check on startup via `OMNIROUTE_SKIP_DB_HEALTHCHECK=1` — replaces slow `integrity_check` (7+ min on large WAL) with `quick_check`, and adds env var to skip entirely. ([#2554](https://github.com/diegosouzapw/OmniRoute/pull/2554) — thanks @soyelmismo) -- **refactor(dashboard):** Provider Quota grouped layout with vertical rail — restructures the page to a 2-column per-provider layout (left rail with icon/name/status, right content with dynamic per-provider columns), new `providerColumns.ts` / `ProviderGroup.tsx` / `AccountRow.tsx` components, env chip-filter row, bulk-refresh per group, and inline expanded panels. ([#2528](https://github.com/diegosouzapw/OmniRoute/pull/2528) — thanks @Gi99lin) -- **feat(providers):** add 26 free-tier providers missing from registry — Novita, Avian, Chutes, Kluster, Targon, Nineteen, Celery, Ditto, Atoma, and more. ([#2590](https://github.com/diegosouzapw/OmniRoute/pull/2590) — thanks @oyi77) -- **feat(providers):** add api-airforce free provider with 55 models. ([#2587](https://github.com/diegosouzapw/OmniRoute/pull/2587) — thanks @oyi77) -- **feat(dashboard):** configurable sidebar — presets, drag-and-drop ordering, smart-grouping, and new Settings → Sidebar page. ([#2581](https://github.com/diegosouzapw/OmniRoute/pull/2581) — thanks @Gi99lin) +- **feat(@omniroute/opencode-plugin):** 模型显示名称中添加上游服务商后缀 — 在丰富名称后追加服务商标签(如 `Claude Opus 4.7 · Claude` vs `Claude Opus 4.7 · Kiro`),使 OC TUI 模型选择器能区分通过不同上游连接路由的同 ID 模型。默认开启,可通过 `features.providerTag: false` 关闭。([#2602](https://github.com/diegosouzapw/OmniRoute/pull/2602) — 感谢 @mrmm) +- **feat(@omniroute/opencode-plugin):** 服务商标签改为前缀 + 交通灯压缩表情 — 服务商标签现在前置显示(`Claude - Claude Opus 4.7`)以改善 TUI 列分组,长标签智能缩写(`GitHub Models` → `GHM`)。压缩管线以表情渲染强度(🟢🟡🟠🔴)。([#2604](https://github.com/diegosouzapw/OmniRoute/pull/2604) — 感谢 @mrmm) +- **feat(providers):** 新增 7 个免费层服务商(第一波)— Arcee AI、InclusionAI、Krutrim、Liquid AI、MonsterAPI、Nomic 和 Poolside 现已作为新的 API-key 服务商提供,包含服务商图标、模型规格和完整路由支持。([#2479](https://github.com/diegosouzapw/OmniRoute/pull/2479) — 感谢 @oyi77) +- **feat(providers):** 新增 Astraflow 服务商支持,含全球 + 中国端点 — 新服务商提供双区域基础 URL,支持全球和中国大陆访问。([#2486](https://github.com/diegosouzapw/OmniRoute/pull/2486) — 感谢 @ucloudnb666) +- **feat(providers):** 新增 `claude-web` 服务商 — 基于 Cookie 的 Claude Web 聊天访问,无需 OAuth。([#2476](https://github.com/diegosouzapw/OmniRoute/pull/2476) — 感谢 @oyi77) +- **feat(providers):** 新增 14 个免费层服务商(第一波 b)— 360AI、百川、百度、字节/豆包、IDEO、快手/可灵、昆仑/Skywork、商汤/SenseNova、阶跃星辰、腾讯混元、智谱 GLM、Replicate、RunPod 和 Modal,包含服务商图标、模型规格和路由支持。([#2488](https://github.com/diegosouzapw/OmniRoute/pull/2488) — 感谢 @oyi77) +- **feat(hermes):** 新增丰富的多角色 Hermes Agent CLI 支持 — 7 个可配置角色(default、delegation、vision、compression、web_extract、skills_hub、approval),每个角色可选择模型并生成 YAML 配置,仪表盘卡片带预览和首页小组件集成。([#2526](https://github.com/diegosouzapw/OmniRoute/pull/2526) — 感谢 @apoapostolov) +- **feat(cloud-agents):** 云端 Agent UX 全面改版 — 标签页(tasks/agents/settings)、状态过滤器、Material 图标、时长格式化、云端 Agent 凭证和健康 API 端点、内存统计端点。([#2516](https://github.com/diegosouzapw/OmniRoute/pull/2516) — 感谢 @oyi77) +- **feat(authz):** 具有 manage 作用域的 API key 可从非本地地址访问 `/api/mcp/*` — Route Guard Tiers 系统(LOCAL_ONLY / ALWAYS_PROTECTED / MANAGEMENT),为受 `manage` 作用域控制的远程 MCP 访问开辟窄通道;`/api/cli-tools/runtime/*` 保持严格本地限制。包含仪表盘 AuthzSection、清单 API 和完整文档。([#2473](https://github.com/diegosouzapw/OmniRoute/pull/2473) — 感谢 @mrmm) +- **feat(home):** 为高级用户定制首页 — 将服务商配额固定到首页,通过外观设置开关快速入门和服务商拓扑的可见性。([#2531](https://github.com/diegosouzapw/OmniRoute/pull/2531) — 感谢 @apoapostolov) +- **feat(home):** 服务商配额自动刷新 — 可配置间隔(60s–600s),在外观设置中可开关;自动刷新固定在首页的配额信息。([#2532](https://github.com/diegosouzapw/OmniRoute/pull/2532) — 感谢 @apoapostolov) +- **feat(@omniroute/opencode-plugin):** OmniRoute OpenCode 插件 — 从 OmniRoute API 获取实时模型,Combo 感知的模型列表,Gemini 请求清洗,多实例支持,认证流程集成,以及 10 个测试文件。([#2529](https://github.com/diegosouzapw/OmniRoute/pull/2529) — 感谢 @mrmm) +- **feat(executors):** 将 OpenCode 客户端头转发到上游服务商 — OpenCode 特定头现在通过 executor 管线转发以改善兼容性。([#2538](https://github.com/diegosouzapw/OmniRoute/pull/2538) — 感谢 @kang-heewon) +- **feat(fireworks):** 新增模型并支持 `modelIdPrefix` — 通用注册表字段,存储短模型 ID 并在上游 API 调用前拼接完整路径前缀。新增 6 个 Fireworks 模型、`modelsUrl` 动态同步和 Qwen3 重排序器。([#2560](https://github.com/diegosouzapw/OmniRoute/pull/2560) — 感谢 @HALDRO) +- **feat(@omniroute/opencode-plugin):** 可读 + 可过滤 + 离线韧性模型选择器 — `usableOnly` 过滤器(仅显示连接健康的服务商)、`diskCache` 离线水合、`Combo:` 前缀标签、压缩元数据标签。([#2572](https://github.com/diegosouzapw/OmniRoute/pull/2572) — 感谢 @mrmm) +- **feat(smart-pipeline):** 自动 Combo 路由的多阶段管线 — 基于规则 + 意图分类器 + 领域特定阶段,含可配置管线路由器、准确率基准测试和全面测试。([#2551](https://github.com/diegosouzapw/OmniRoute/pull/2551) — 感谢 @oyi77) +- **feat(ops):** 通过 `OMNIROUTE_SKIP_DB_HEALTHCHECK=1` 跳过启动时 DB 健康检查 — 将缓慢的 `integrity_check`(大 WAL 上需 7 分钟以上)替换为 `quick_check`,并添加环境变量以完全跳过。([#2554](https://github.com/diegosouzapw/OmniRoute/pull/2554) — 感谢 @soyelmismo) +- **refactor(dashboard):** 服务商配额分组布局,含垂直侧栏 — 将页面重构为每个服务商 2 列布局(左侧栏含图标/名称/状态,右侧内容含动态按服务商列),新增 `providerColumns.ts` / `ProviderGroup.tsx` / `AccountRow.tsx` 组件,环境芯片过滤行,按组批量刷新和内联展开面板。([#2528](https://github.com/diegosouzapw/OmniRoute/pull/2528) — 感谢 @Gi99lin) +- **feat(providers):** 新增注册表中缺失的 26 个免费层服务商 — Novita、Avian、Chutes、Kluster、Targon、Nineteen、Celery、Ditto、Atoma 等。([#2590](https://github.com/diegosouzapw/OmniRoute/pull/2590) — 感谢 @oyi77) +- **feat(providers):** 新增 api-airforce 免费服务商,含 55 个模型。([#2587](https://github.com/diegosouzapw/OmniRoute/pull/2587) — 感谢 @oyi77) +- **feat(dashboard):** 可配置侧边栏 — 预设、拖拽排序、智能分组,以及新的设置 → 侧边栏页面。([#2581](https://github.com/diegosouzapw/OmniRoute/pull/2581) — 感谢 @Gi99lin) -### 🔧 Bug Fixes +### 🔧 问题修复 -- **fix(validation):** stop appending a second `/models` when the Gemini base URL already ends in `/models` — Google AI Studio connections using the default base URL were validating against `.../v1beta/models/models` and failing with `404` for every connection. ([#2545](https://github.com/diegosouzapw/OmniRoute/issues/2545)) -- **fix(cloudflare-ai):** flatten OpenAI content-part arrays to plain strings for the Workers AI (`cf/`) executor — Workers AI's `/ai/v1/chat/completions` rejects `content: [{type:"text",...}]` with HTTP 400, so requests with array content now have their text parts joined into a string. ([#2539](https://github.com/diegosouzapw/OmniRoute/issues/2539)) -- **fix(i18n):** replace leftover Portuguese strings in the English source with English on the Quota dashboards — the quota-share Beta notice (`betaConfigSaved*`) and the Provider Quota row's `Edit cutoffs` / `Refresh now` fallbacks were showing Portuguese. ([#2540](https://github.com/diegosouzapw/OmniRoute/issues/2540)) +- **fix(validation):** 当 Gemini 基础 URL 已以 `/models` 结尾时不再追加第二个 `/models` — 使用默认基础 URL 的 Google AI Studio 连接此前验证请求会访问 `.../v1beta/models/models` 并返回 `404`。([#2545](https://github.com/diegosouzapw/OmniRoute/issues/2545)) +- **fix(cloudflare-ai):** 将 Workers AI (`cf/`) executor 的 OpenAI content-part 数组展平为纯字符串 — Workers AI 的 `/ai/v1/chat/completions` 会以 HTTP 400 拒绝 `content: [{type:"text",...}]`,现在文本部分会合并为字符串。([#2539](https://github.com/diegosouzapw/OmniRoute/issues/2539)) +- **fix(i18n):** 将配额仪表盘中英文源文件里残留的葡萄牙语字符串替换为英文 — 配额分享 Beta 通知 (`betaConfigSaved*`) 和服务商配额行中 `Edit cutoffs` / `Refresh now` 的回退文本此前显示为葡萄牙语。([#2540](https://github.com/diegosouzapw/OmniRoute/issues/2540)) -- **fix(proxy):** honor the legacy per-provider/global proxy config in `resolveProxyForProvider` — the Claude OAuth token exchange and token refresh only consulted the new proxy registry, so a proxy configured the legacy way (`/api/settings/proxy?level=provider`) was ignored and the exchange went out directly from the host, tripping Anthropic's IP `rate_limit_error` on VPS deployments. It now falls back to the legacy config, mirroring `resolveProxyForConnection`. ([#2456](https://github.com/diegosouzapw/OmniRoute/issues/2456)) -- **fix(antigravity):** auto-discover a missing Cloud Code `projectId` via `loadCodeAssist` before failing — a freshly re-added Antigravity account whose stored `projectId` was empty (OAuth-time discovery returned nothing) now recovers the project on the first request instead of returning `422 Missing Google projectId`, mirroring the `gemini-cli` bootstrap. ([#2334](https://github.com/diegosouzapw/OmniRoute/issues/2334), [#2541](https://github.com/diegosouzapw/OmniRoute/issues/2541)) -- **fix(stream):** keep the `/v1/responses` SSE connection warm for strict clients — emit an early keepalive while the upstream produces its first token and lower the heartbeat cadence to 4s, so Codex CLI's `reqwest` client (≈5s idle-read timeout) no longer drops the stream "before completion" on slow/reasoning models. `curl` was unaffected because it has no idle timeout. ([#2544](https://github.com/diegosouzapw/OmniRoute/issues/2544)) -- **fix(electron):** wait longer for the server on first launch and reload once it responds — long post-upgrade DB migrations could exceed the 30s readiness probe, leaving the desktop app stuck on the "Server starting" screen even though the backend was healthy. The probe now targets the auth-exempt health endpoint with a generous timeout and reloads the window once the server comes up. ([#2460](https://github.com/diegosouzapw/OmniRoute/issues/2460)) +- **fix(proxy):** 在 `resolveProxyForProvider` 中遵循旧版按服务商/全局代理配置 — Claude OAuth Token 交换和 Token 刷新仅查询了新的代理注册表,因此以旧方式(`/api/settings/代理?level=服务商`)配置的代理被忽略,导致交换请求直接从主机发出,在 VPS 部署上触发 Anthropic 的 IP `rate_limit_error`。现在会回退到旧版配置,与 `resolveProxyForConnection` 行为一致。([#2456](https://github.com/diegosouzapw/OmniRoute/issues/2456)) +- **fix(antigravity):** 在失败前通过 `loadCodeAssist` 自动发现缺失的 Cloud Code `projectId` — 新重新添加的 Antigravity 账户如果存储的 `projectId` 为空(OAuth 发现时未返回),现在会在首次请求时恢复项目信息,而非返回 `422 Missing Google projectId`,与 `gemini-cli` 启动流程一致。([#2334](https://github.com/diegosouzapw/OmniRoute/issues/2334), [#2541](https://github.com/diegosouzapw/OmniRoute/issues/2541)) +- **fix(stream):** 为严格客户端保持 `/v1/responses` SSE 连接活跃 — 在上游生成首个 Token 前发送早期 keepalive,并将心跳间隔降至 4s,使 Codex CLI 的 `reqwest` 客户端(≈5s 空闲读取超时)不再在慢速/推理模型上丢弃流 "before 补全"。`curl` 不受影响,因为它没有空闲超时。([#2544](https://github.com/diegosouzapw/OmniRoute/issues/2544)) +- **fix(electron):** 首次启动时延长服务器等待时间,并在响应后重新加载 — 长时间的上线后 DB 迁移可能超过 30s 就绪探测,导致桌面应用卡在 "Server starting" 屏幕。探测现在以宽松超时针对无需认证的健康端点,并在服务器就绪后重新加载窗口。([#2460](https://github.com/diegosouzapw/OmniRoute/issues/2460)) -- **fix(cli):** mark `bin/omniroute.mjs` as executable (mode 755) so the globally-installed CLI runs directly without a manual `chmod +x`. ([#2469](https://github.com/diegosouzapw/OmniRoute/issues/2469) — thanks @disonjer) -- **fix(settings):** restore the Global System Prompt into the in-memory config on server startup and after JSON/SQLite import — it was only loaded by the PUT endpoint, so the toggle/prompt silently reverted to defaults after any restart or import. ([#2470](https://github.com/diegosouzapw/OmniRoute/issues/2470) — thanks @disonjer) -- **fix(settings):** append the Global System Prompt **after** existing system content instead of prepending it, so provider/agent instructions (Kiro, OpenCode, Hermes, …) injected into the system message no longer override the user's global prompt via recency bias. ([#2468](https://github.com/diegosouzapw/OmniRoute/issues/2468) — thanks @disonjer) -- **fix(kiro):** refresh imported social tokens (`authMethod === "imported"`) via the Kiro social-auth endpoint instead of AWS SSO OIDC — imported tokens carry a registered `clientId`/`clientSecret` but a social-issued refresh token the OIDC client cannot refresh, so auto-refresh was failing with "provider returned no new token". ([#2467](https://github.com/diegosouzapw/OmniRoute/issues/2467) — thanks @disonjer) -- **fix(antigravity):** resolve the Cloud Code `projectId` from `providerSpecificData` as a fallback (and preserve it across token refresh) so the Gemini `/v1beta` streaming path stops returning a spurious `422 Missing Google projectId` for connections that store the project there. ([#2480](https://github.com/diegosouzapw/OmniRoute/issues/2480)) -- **fix(api):** `GET /v1beta/models` now lists only models whose provider has an active/validated connection, matching the OpenAI-format `/v1/models` behavior, instead of returning the entire catalog. ([#2483](https://github.com/diegosouzapw/OmniRoute/issues/2483)) +- **fix(cli):** mark `bin/omniroute.mjs` as executable (mode 755) so the globally-installed CLI runs directly without a manual `chmod +x`. ([#2469](https://github.com/diegosouzapw/OmniRoute/issues/2469) — 感谢 @disonjer) +- **fix(settings):** 在服务器启动和 JSON/SQLite 导入后将全局 System Prompt 恢复到内存配置中 — 此前仅在 PUT 端点加载,因此任何重启或导入后开关/prompt 都会静默回退到默认值。([#2470](https://github.com/diegosouzapw/OmniRoute/issues/2470) — 感谢 @disonjer) +- **fix(settings):** 将全局 System Prompt 追加到**现有系统内容之后**而非之前,使注入系统消息的服务商/代理指令(Kiro、OpenCode、Hermes 等)不再因近因效应覆盖用户的全局 prompt。([#2468](https://github.com/diegosouzapw/OmniRoute/issues/2468) — 感谢 @disonjer) +- **fix(kiro):** 通过 Kiro social-auth 端点而非 AWS SSO OIDC 刷新已导入的社交 Token(`authMethod === "imported"`)— 已导入的 Token 携带已注册的 `clientId`/`clientSecret`,但刷新 Token 由社交渠道签发,OIDC 客户端无法刷新,因此自动刷新失败并提示 "provider returned no new token"。([#2467](https://github.com/diegosouzapw/OmniRoute/issues/2467) — 感谢 @disonjer) +- **fix(antigravity):** 从 `providerSpecificData` 中解析 Cloud Code `projectId` 作为容灾回退(并在 Token 刷新期间保留),使将项目存储在该处的连接不再在 Gemini `/v1beta` 流式路径中返回虚假的 `422 Missing Google projectId` 错误。([#2480](https://github.com/diegosouzapw/OmniRoute/issues/2480)) +- **fix(api):** `GET /v1beta/models` 现在仅列出其服务商拥有活跃/已验证连接的模型,与 OpenAI 格式的 `/v1/models` 行为一致,而非返回整个目录。([#2483](https://github.com/diegosouzapw/OmniRoute/issues/2483)) -- **fix(cli):** persist `STORAGE_ENCRYPTION_KEY` into `DATA_DIR` (not only `~/.omniroute`) and refuse to auto-generate a fresh key when a `storage.sqlite` already exists — a new key cannot decrypt previously-encrypted credentials, so silently regenerating it locked users out of their database. The CLI now mirrors the server `bootstrapEnv` guard. (reported by Daniel Nach; original key persistence by @Chewji9875 — follow-up to [#1622](https://github.com/diegosouzapw/OmniRoute/issues/1622)) -- **fix(gemini):** preserve and re-attach the `thoughtSignature` on Gemini thinking-model tool calls — thread the signature namespace through the `FORMATS.GEMINI` and `FORMATS.GEMINI_CLI` request translators so the cached signature (keyed by connection + tool-call id) is found on the follow-up turn. Fixes `[400]: Function call is missing a thought_signature in functionCall parts` on agentic Gemini tool use. ([#2504](https://github.com/diegosouzapw/OmniRoute/issues/2504)) -- **fix(translator):** accept PDFs sent in the Responses-API `input_file` shape on the Gemini path, and the Gemini-style `document` shape on the Responses/Codex path — content parts are now normalized across `input_file` / `file` / `document` so a PDF reaches the model regardless of which field name the client used. ([#2515](https://github.com/diegosouzapw/OmniRoute/issues/2515)) -- **fix(stream):** count `thinking` arrays and `reasoning_details` as useful stream output — a reasoning-only response (e.g. Mistral/StepFun with a low `max_tokens`) was misclassified as "Stream ended before producing useful content" and turned into a spurious 502; it is now recognized as valid output. ([#2520](https://github.com/diegosouzapw/OmniRoute/issues/2520)) -- **fix(claude):** extract system/developer role messages in Claude Code semantic passthrough paths — moves `role:"system"` / `role:"developer"` messages from the `messages[]` array to the top-level `system` parameter before sending to Anthropic, which rejects them inside messages. Fixes memory injection context being silently dropped. ([#2497](https://github.com/diegosouzapw/OmniRoute/pull/2497) — thanks @unitythemaker) -- **fix(vision-bridge):** auto-route non-standard provider models through OmniRoute self-loop — vision-bridge now detects when a model doesn't natively support vision and automatically re-routes the image through OmniRoute's own endpoint for format translation. ([#2487](https://github.com/diegosouzapw/OmniRoute/pull/2487) — thanks @herjarsa) -- **fix(mitm):** add IPv6 DNS redirect, modular antigravity target, improved logging — MITM DNS handler now correctly redirects IPv6 (AAAA) queries alongside IPv4, adds a dedicated `antigravity.ts` target module, and enhances DNS/TLS logging for debugging. ([#2514](https://github.com/diegosouzapw/OmniRoute/pull/2514) — thanks @herjarsa) -- **fix(usage):** improve Claude and MiniMax plan label detection — better tier name resolution for Claude OAuth usage (tier/plan/subscription_type/org fields) and new MiniMax plan label inference from quota totals. ([#2498](https://github.com/diegosouzapw/OmniRoute/pull/2498) — thanks @Gi99lin) -- **fix(codex):** fan out image `n` requests in parallel — when Codex requests `n > 1` images, the image-generation handler now dispatches them concurrently instead of sequentially, significantly reducing total latency. ([#2499](https://github.com/diegosouzapw/OmniRoute/pull/2499) — thanks @nmime) -- **fix(embeddings):** strip stale `Content-Encoding` headers from upstream response — prevents clients from receiving gzip-encoded responses with `identity` encoding declared, which caused silent data corruption. ([#2477](https://github.com/diegosouzapw/OmniRoute/pull/2477) — thanks @lordavadon2) -- **fix(model):** return clear error instead of silent OpenAI default for unrecognized models — previously, an unrecognized model silently fell back to OpenAI; now returns a 404 with a descriptive message listing known providers. ([#2492](https://github.com/diegosouzapw/OmniRoute/pull/2492) — thanks @herjarsa) -- **fix(dark-mode):** correct background token on Compression Override select — the combo compression override `` 使用了硬编码白色背景,在暗色模式下不可见。([#2513](https://github.com/diegosouzapw/OmniRoute/pull/2513) — 感谢 @apoapostolov) +- **fix(antigravity):** 将订阅套餐检测与 Antigravity Manager 对齐 — `extractCodeAssistSubscriptionTier` 现在从 `loadCodeAssist` 响应中解析正确的嵌套字段,新的 `extractCodeAssistOnboardTierId` 回退处理引导流程。订阅信息按 access-token 缓存,TTL 5 分钟。([#2496](https://github.com/diegosouzapw/OmniRoute/pull/2496) — 感谢 @Gi99lin) +- **fix(opencode-zen):** 添加 `opencode` 服务商别名并与线上 API 同步模型列表 — `opencode-zen` 和 `opencode-go` 现在也可通过更短的 `opencode` 别名访问,默认模型列表与线上 `/v1/models` 目录保持同步。([#2508](https://github.com/diegosouzapw/OmniRoute/pull/2508) — 感谢 @herjarsa) +- **fix(combo):** 当 combo 目标因凭证不可用而跳过时,改进日志消息 — 此前记录了误导性的 "服务商 not found";现在显示 "skipped: 凭证 unavailable"。([#2494](https://github.com/diegosouzapw/OmniRoute/pull/2494) — 感谢 @herjarsa) +- **fix(security):** 在 `generateTaskId`/`ActivityId` 中用 `crypto.randomUUID` 替换 `Math.random`,并修复测试中的 URL hostname 检查 — 消除 CodeQL 标记的弱 PRNG 使用。([#2489](https://github.com/diegosouzapw/OmniRoute/pull/2489)) +- **fix(electron):** 降级到 Electron 41.x 以保证 better-sqlite3 V8 兼容性 — Electron 42.x 搭载的 V8 版本破坏了 `better-sqlite3` 原生绑定;锁定 41.x 恢复稳定性。 +- **fix(@omniroute/opencode-provider):** 在模型条目中包含 `limit.context` 以便 OpenCode 检测上下文窗口 — OpenCode 读取 `limit.context` 来确定可用于压缩和溢出检测的上下文长度。 +- **fix(providers):** 使 `gitlawb/gitlawb-gmi` 模型条目变为可选 — 防止该模型不在目录中时服务商初始化失败。([#2476](https://github.com/diegosouzapw/OmniRoute/pull/2476) — 感谢 @oyi77) +- **fix(translator):** 当目标服务商使用 Responses API 时,以 Responses-API 扁平工具格式 (`{ type, name }`) 注入 `omniroute_web_search` — 此前始终以 Chat Completions 嵌套格式发送,导致 Codex/中继上游拒绝请求。([#2390](https://github.com/diegosouzapw/OmniRoute/issues/2390)) +- **fix(kiro):** 在发送到 CodeWhisperer 前序列化非字符串 `role:"tool"` 消息内容 — 结构化/数组工具输出会坍缩为 `content:[{ text: "" }]`,Kiro 以 `400 Improperly formed 请求` 拒绝。([#2446](https://github.com/diegosouzapw/OmniRoute/issues/2446)) +- **fix(claude):** 将 heavy-agent beta 头(`context-1m`、`effort`、`advanced-tool-use`)限制为仅 Opus/Sonnet 使用 — OAuth 下的 Haiku 收到 `context-1m` 会以 400 拒绝。同时清洗透传中的历史 `thinking` 块签名。([#2454](https://github.com/diegosouzapw/OmniRoute/issues/2454) — 感谢 @havockdev) +- **fix(perplexity-web):** 通过模拟 Firefox-148 TLS 的客户端路由请求,使 Perplexity 的 Cloudflare 边缘不再以 403 质询拒绝 VPS/数据中心 IP。([#2459](https://github.com/diegosouzapw/OmniRoute/issues/2459) — 感谢 @havockdev) +- **fix(validation):** 在服务商连接测试路径中调用 `.startsWith()` / `.trim()` 前,保护 `apiKey`/`modelsUrl` 防止非字符串值。([#2463](https://github.com/diegosouzapw/OmniRoute/issues/2463)) +- **fix(cost):** 防止 `cache_creation_input_tokens` 双重计费 — Token 提取器中的 `prompt_tokens` 已包含 `cache_read` 和 `cache_creation`,因此 `nonCachedInput` 现在减去两种缓存类型,避免按完整输入费率对缓存定价。([#2522](https://github.com/diegosouzapw/OmniRoute/pull/2522) — 感谢 @herjarsa) +- **fix(handler):** 始终在 Claude 透传路径中规范化系统角色消息 — `normalizeClaudeUpstreamMessages()` 现在在 `compatibleBridge` 和纯透传中均无条件调用,确保 `role:"system"` 消息始终被提取到顶层 `system` 参数。([#2519](https://github.com/diegosouzapw/OmniRoute/pull/2519) — 感谢 @herjarsa) +- **fix(handler):** 在非流式响应路径中捕获 Gemini `thought_signature` — 非流式翻译器现在从 Gemini thinking 模型部分捕获 `thoughtSignature` 并持久化,使后续轮次能正确解析。([#2518](https://github.com/diegosouzapw/OmniRoute/pull/2518) — 感谢 @herjarsa) +- **fix(kiro):** 用设备流替换损坏的社交 OAuth — 将 Kiro 的 Google/GitHub 社交登录从损坏的 PKCE `kiro://` 自定义协议重写为 AWS Cognito 设备流,在 web/代理环境中正常工作。([#2524](https://github.com/diegosouzapw/OmniRoute/pull/2524) — 感谢 @disonjer) +- **fix(providers):** 解决 `opencode/` → `opencode-zen` 别名不匹配 + 新增 40+ 模型 — `opencode` 现在在 executor、模型解析器和服务商注册表中作为 `opencode-zen` 的正式别名;新增 GPT 5.x、Claude 4.x、Gemini 3.x、Grok、Kimi 等模型并附带测试。([#2517](https://github.com/diegosouzapw/OmniRoute/pull/2517) — 感谢 @herjarsa) +- **fix(antigravity):** 停滞的 Antigravity 会话自动容灾 — 新增 `ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE` 共享常量用于预响应超时检测,当会话在收到头部前停滞时自动容灾到下一个账户。Node.js 引擎范围放宽到 `>=20.20.2`。([#2464](https://github.com/diegosouzapw/OmniRoute/pull/2464) — 感谢 @dhaern) +- **fix(deepseek-web):** 修复 SSE 解析器、prompt 格式和错误处理 — 处理所有 3 种 DeepSeek SSE 流格式(初始片段、APPEND 操作、裸字符串 token),简化 prompt 为单轮次防止聊天标记泄漏,在提取 Token 前检查 `json.code`。([#2502](https://github.com/diegosouzapw/OmniRoute/pull/2502) — 感谢 @ovehbe) +- **fix(codex):** 导入时接受不含 `auth_mode` 字段的 `auth.json` — Codex CLI 不再写入 `auth_mode`;导入现在接受两种格式,只要必需的 Token 存在即可。语义缓存读取现在要求显式 `temperature: 0`。([#2536](https://github.com/diegosouzapw/OmniRoute/pull/2536) — 感谢 @janeza2) +- **fix(freetheai):** 在 baseUrl 中添加 `/chat/completions` 以解决 404 错误。([#2557](https://github.com/diegosouzapw/OmniRoute/pull/2557) — 感谢 @lordavadon2) +- **fix(qoder):** 将 PAT Token 路由到 Qoder 原生 API 而非 DashScope — 检测 `pt-` 前缀的 Token 并路由到 `api.qoder.com`,附带正确的 User-Agent 头。([#2559](https://github.com/diegosouzapw/OmniRoute/pull/2559) — 感谢 @herjarsa) +- **fix(perf):** 在 RTK 压缩热路径中缓存编译后的 RegExp — 消除每秒数千次冗余的 `new RegExp()` 实例化。([#2553](https://github.com/diegosouzapw/OmniRoute/pull/2553) — 感谢 @soyelmismo) +- **fix(reasoning-cache):** 模块加载时自动启动定期清理 — `server-init.ts` 任务从未被导入(死代码),导致 `reasoning_cache` 表无限增长。现在自动运行 30 分钟清理周期。([#2552](https://github.com/diegosouzapw/OmniRoute/pull/2552) — 感谢 @soyelmismo) +- **fix(claude):** Sonnet 省略 `context-1m` beta — 限制为仅 Opus 使用,避免长上下文积分门槛错误。新增 `afk-mode-2026-01-31`,将 `redact-thinking` 替换为 `thinking-token-count-2026-05-13`。([#2568](https://github.com/diegosouzapw/OmniRoute/pull/2568) — 感谢 @unitythemaker) +- **fix(codex):** 在前端导入预览中放宽 `auth_mode` 检查 — 接受 `undefined`/`null`/`"chatgpt"` 而非严格要求 `"chatgpt"`,与 #2536 的后端修复保持一致。([#2567](https://github.com/diegosouzapw/OmniRoute/pull/2567) — 感谢 @janeza2) +- **fix(kimi):** 在全部 4 层中声明 Kimi K2.6 的视觉能力 — `providerRegistry`、`modelSpecs`、`catalog.ts` 关键词列表和 Playground `VISION_MODELS`;此前该模型会静默拒绝图像上传。([#2573](https://github.com/diegosouzapw/OmniRoute/pull/2573) — 感谢 @herjarsa) +- **fix(dashboard):** 请求日志查看器支持超过 300 行分页 — `getCallLogs` 现在接受 `offset` 并使用参数化 SQL(消除字符串插值 `LIMIT`);`RequestLoggerV2` 通过 "加载更多" + IntersectionObserver 无限滚动扩展窗口,过滤器变更时重置。([#2576](https://github.com/diegosouzapw/OmniRoute/pull/2576)) +- **fix(cli):** 使用 `/api/monitoring/health` 进行服务器就绪检查 — `waitForServer()` 此前轮询了需要认证的 `/api/health`(401),导致 `omniroute serve` 无限挂起。([#2578](https://github.com/diegosouzapw/OmniRoute/pull/2578) — 感谢 @amogus22877769) +- **fix(combo):** 通过结构化错误码 + 正则回退检测无效模型错误 — 当 Combo 目标拒绝模型时(如免费账户 vs Pro),路由器现在能识别 `model_not_found` / `deployment_not_found` 码和 6 种正则模式,并容灾到下一个目标而非停止循环。([#2534](https://github.com/diegosouzapw/OmniRoute/pull/2534) — 感谢 @HALDRO) +- **fix(security):** 审查后加固批次 — `spawnSync` 参数数组替换 `execSync` 字符串模板(命令注入),CSP `unsafe-eval` 受 `!app.isPackaged` 控制,budget/bulk 和 resilience/reset 端点添加 `requireManagementAuth` 守卫,gemini-web/claude-web/copilot-web/oauth/agents catch 块中清洗错误消息,熔断器持久化 `lastFailureKind`,combo 在每次 set-retry 迭代时重置 `exhaustedProviders`。([#2435](https://github.com/diegosouzapw/OmniRoute/pull/2435)) +- **fix(@omniroute/opencode-plugin):** 遵循 `geminiSanitization` 和 `fetchInterceptor` 特性标志 — 此前两者均无条件应用;现在每个 fetch 层受其标志控制(默认开启),禁用两者则回退到普通 SDK fetch。([#2546](https://github.com/diegosouzapw/OmniRoute/pull/2546)) +- **fix(#2575):** 在 `arePrivateProviderUrlsAllowed()` 中检查 DB 特性标志覆盖 — 支持运行时切换无需重启。([#2595](https://github.com/diegosouzapw/OmniRoute/pull/2595) — 感谢 @herjarsa) +- **fix(mimo):** 为 MiMo-V2.5、V2.5-Pro 和 V2-Omni 添加 `supportsVision` 标志 — 此前图像上传会被静默拒绝。([#2592](https://github.com/diegosouzapw/OmniRoute/pull/2592) — 感谢 @herjarsa) +- **fix(ops):** 将 `OMNIROUTE_SKIP_DB_HEALTHCHECK` 环境变量传播到定期 DB 健康检查调度器 — #2554 的配套修复。([#2591](https://github.com/diegosouzapw/OmniRoute/pull/2591) — 感谢 @soyelmismo) +- **fix(github):** 从 GitHub Copilot 的 Haiku/Sonnet 模型中移除错误的 `openai-responses` targetFormat。([#2583](https://github.com/diegosouzapw/OmniRoute/pull/2583) — 感谢 @oyi77) +- **fix(copilot):** 稳定 responses 配置 — 移除 865 行不稳定的配置,简化处理器。([#2579](https://github.com/diegosouzapw/OmniRoute/pull/2579) — 感谢 @ivan-mezentsev) +- **fix(#2544):** 为 Responses API 转换流添加 SSE 心跳 keepalive — 防止 Codex CLI 0.130.0 在长思考/推理阶段断开连接。([#2599](https://github.com/diegosouzapw/OmniRoute/pull/2599) — 感谢 @herjarsa) +- **fix(memory):** 在语义透传路径中提取系统角色消息,防止记忆注入时出现 400 — 系统消息此前被原样传递给拒绝混合角色的服务商。([#2474](https://github.com/diegosouzapw/OmniRoute/pull/2474) — 感谢 @Tentoxa) +- **fix(@omniroute/opencode-provider):** 在模型条目中包含 `limit.context` 以便 OpenCode 检测上下文窗口 — 此前 OpenCode 无法确定模型上下文大小。([#2482](https://github.com/diegosouzapw/OmniRoute/pull/2482) — 感谢 @herjarsa) +- **fix(mimo):** 在 providerRegistry 中为 Kimi K2.6 添加 `supportsVision` 标志 + MiMo V2.5/V2.5-Pro/V2-Omni 的全面视觉测试。([#2600](https://github.com/diegosouzapw/OmniRoute/pull/2600) — 感谢 @herjarsa) +- **fix(proxy):** 优先使用限定作用域的代理而非注册表全局回退 — 旧版按服务商的代理在两个存储后端上被注册表全局容灾覆盖。解析现在遵循严格优先级:账户 → 服务商 → Combo → 全局。([#2606](https://github.com/diegosouzapw/OmniRoute/pull/2606) — 感谢 @terence71-glitch) +- **fix(@omniroute/opencode-plugin):** 规范双胞胎去重 + 别名回退丰富 — `/v1/models` 以别名 (`cc/claude-opus-4-7`) 和规范名 (`claude/claude-opus-4-7`) 返回同一模型;现在丢弃约 75 个规范重复项,并通过别名索引回退拯救约 88 个原始 ID 行并附加正确服务商前缀。同时在静态目录中输出 `cost`、`release_date`、`modalities` 字段,并将服务商标签阈值提升到 12 字符(保留 `AssemblyAI`、`Antigravity` 全名)。([#2607](https://github.com/diegosouzapw/OmniRoute/pull/2607) — 感谢 @mrmm) +- **fix(registry):** 为 HuggingFace(6 个模型)和 HackClub(3 个模型)填充空模型数组 + 将 Snowflake 占位 baseUrl 修复为 `{account}` 模板模式。([#2611](https://github.com/diegosouzapw/OmniRoute/pull/2611) — 感谢 @oyi77) ### 🌐 Internationalization -- **i18n(zh-CN):** translate 830 missing UI strings — replaces all `__MISSING__:` placeholders with proper Chinese translations. ([#2523](https://github.com/diegosouzapw/OmniRoute/pull/2523) — thanks @InkshadeWoods) -- **i18n(dashboard):** add missing dashboard keys and fix EN fallbacks — hundreds of hardcoded English strings across cache, caveman, costs, skills, memory, and evals pages replaced with `t()` calls. ([#2500](https://github.com/diegosouzapw/OmniRoute/pull/2500) — thanks @Gi99lin) -- **i18n(pt-BR):** complete and fix Brazilian Portuguese translation — comprehensive overhaul of pt-BR locale with ~3000 lines of quality translations, filling all missing keys and correcting existing entries. ([#2543](https://github.com/diegosouzapw/OmniRoute/pull/2543) — thanks @alltomatos) -- **i18n(ru):** comprehensive Russian translation update — ~2000 lines of corrected and filled translations. ([#2550](https://github.com/diegosouzapw/OmniRoute/pull/2550) — thanks @AgentAlexAI) -- **i18n(all):** comprehensive localization and UI refactoring — 42 locale files synchronized with missing keys, cloud-agents page i18n rewrite, and consistent `t()` usage across 21 dashboard components. ([#2580](https://github.com/diegosouzapw/OmniRoute/pull/2580) — thanks @alltomatos) -- **i18n(all):** translate freeTier provider strings across 41 locales — replaces `__MISSING__:Free Tier Providers` placeholders with proper translations in both `common` and `providers` namespaces. ([#2609](https://github.com/diegosouzapw/OmniRoute/pull/2609) — thanks @leninejunior) -- **i18n(pt-BR):** eliminate all 1270 remaining `__MISSING__` markers — completes pt-BR translation across 41 namespaces to true 100% coverage. ([#2610](https://github.com/diegosouzapw/OmniRoute/pull/2610) — thanks @leninejunior) +- **i18n(zh-CN):** 翻译 830 个缺失的 UI 字符串 — 将所有 `__MISSING__:` 占位符替换为正确的中文翻译。([#2523](https://github.com/diegosouzapw/OmniRoute/pull/2523) — 感谢 @InkshadeWoods) +- **i18n(dashboard):** 添加缺失的仪表盘 key 并修复英文回退 — 缓存、caveman、costs、skills、memory 和 evals 页面中数百个硬编码英文字符串替换为 `t()` 调用。([#2500](https://github.com/diegosouzapw/OmniRoute/pull/2500) — 感谢 @Gi99lin) +- **i18n(pt-BR):** 完成并修复巴西葡萄牙语翻译 — 全面翻新 pt-BR 语言包,约 3000 行高质量翻译,填充所有缺失 key 并修正已有条目。([#2543](https://github.com/diegosouzapw/OmniRoute/pull/2543) — 感谢 @alltomatos) +- **i18n(ru):** 全面更新俄语翻译 — 约 2000 行修正和填充的翻译。([#2550](https://github.com/diegosouzapw/OmniRoute/pull/2550) — 感谢 @AgentAlexAI) +- **i18n(all):** 全面本地化和 UI 重构 — 42 个语言文件同步缺失 key,cloud-agents 页面 i18n 重写,21 个仪表盘组件统一使用 `t()`。([#2580](https://github.com/diegosouzapw/OmniRoute/pull/2580) — 感谢 @alltomatos) +- **i18n(all):** 翻译 41 个语言包中的 freeTier 服务商字符串 — 将 `__MISSING__:Free Tier Providers` 占位符替换为 `common` 和 `服务商` 命名空间中的正确翻译。([#2609](https://github.com/diegosouzapw/OmniRoute/pull/2609) — 感谢 @leninejunior) +- **i18n(pt-BR):** 消除全部 1270 个剩余 `__MISSING__` 标记 — 完成 41 个命名空间的 pt-BR 翻译,达��真正 100% 覆盖率。([#2610](https://github.com/diegosouzapw/OmniRoute/pull/2610) — 感谢 @leninejunior) -### 📝 Maintenance +### 📝 维护 - **chore:** remove Akamai VPS deploy from release workflow and skills. - **chore(deps):** bump `actions/setup-node` from v4 to v6 + `randomBytes` security fix for cloud agent task IDs. ([#2589](https://github.com/diegosouzapw/OmniRoute/pull/2589)) - **chore(deps):** bump `actions/upload-artifact` from v4 to v7. ([#2588](https://github.com/diegosouzapw/OmniRoute/pull/2588)) - **chore:** ignore `.claude/worktrees` from git tracking. -- **chore(ci):** auto-lock release branch on version publish — new CI workflow applies `lock_branch` protection when a GitHub Release is published. ([#2542](https://github.com/diegosouzapw/OmniRoute/pull/2542)) -- **docs:** redesign README — marketing-first layout with accurate provider counts. ([#2490](https://github.com/diegosouzapw/OmniRoute/pull/2490)) +- **chore(ci):** auto-lock release branch on version publish — new CI 工作流 applies `lock_branch` protection when a GitHub Release is published. ([#2542](https://github.com/diegosouzapw/OmniRoute/pull/2542)) +- **docs:** redesign README — marketing-first layout with accurate 服务商 counts. ([#2490](https://github.com/diegosouzapw/OmniRoute/pull/2490)) --- ## [3.8.1] — 2026-05-21 -### ✨ New Features +### ✨ 新功能 -- **feat(settings):** Feature Flags Settings Page (Card Grid + DB overrides) — fully implements the feature flags UI dashboard using Variant A (Card Grid) with Glassmorphism, complete with global `GET/PUT/DELETE` API routes, Zod validation, debounced search, category filters, and full 30+ locale i18n support. Resolves priority hierarchy to DB > ENV > Defaults. ([#2457](https://github.com/diegosouzapw/OmniRoute/pull/2457)) -- **feat(db):** multi-driver SQLite abstraction layer — new `SqliteAdapter` interface with 3 concrete adapters (`betterSqliteAdapter`, `nodeSqliteAdapter`, `sqljsAdapter`) and a `driverFactory` that cascades `better-sqlite3` → `node:sqlite` → `sql.js (WASM)`. Enables OmniRoute to run on any JavaScript runtime (Node.js, Bun, Deno, Cloudflare Workers) without native binary dependencies. `better-sqlite3` moved to `optionalDependencies`. ([#2447](https://github.com/diegosouzapw/OmniRoute/pull/2447)) -- **feat(settings):** Claude Fast Mode toggle in Settings › AI — opt-in toggle that forwards `X-CPA-Force-Fast-Mode` header so a paired CLIProxyAPI build can reach Anthropic Fast Mode (`speed:"fast"`). Model-gated to Opus models matching Anthropic's binary KT() check. ([#2449](https://github.com/diegosouzapw/OmniRoute/pull/2449) — thanks @NomenAK) -- **feat(settings):** Codex Fast Tier — tier dropdown (`default`/`priority`/`flex`) + per-model gate preventing 400 errors from OpenAI when the tier toggle was on for non-Fast-eligible models. ([#2451](https://github.com/diegosouzapw/OmniRoute/pull/2451) — thanks @NomenAK) -- **feat:** align Antigravity 2.0.1 support — updated client profile, upstream headers, and model aliases. ([#2443](https://github.com/diegosouzapw/OmniRoute/pull/2443) — thanks @dhaern) -- **feat:** enhance `extractBearer` to support `x-api-key` for Anthropic API style auth. ([#2436](https://github.com/diegosouzapw/OmniRoute/pull/2436) — thanks @thedtvn) -- **feat(memory):** wire `createMemory` to `upsertSemanticMemoryPoint` (Qdrant). ([#2439](https://github.com/diegosouzapw/OmniRoute/pull/2439) — thanks @NomenAK) +- **feat(settings):** 功能开关设置页面(卡片网格 + 数据库覆盖)— 完整实现了功能开关 UI 控制台,采用方案 A(卡片网格)搭配玻璃态设计,配备全局 `GET/PUT/DELETE` API 路由、Zod 校验、防抖搜索、分类筛选以及完整的 30+ 语言环境国际化支持。优先级解析为 数据库 > 环境变量 > 默认值。([#2457](https://github.com/diegosouzapw/OmniRoute/pull/2457)) +- **feat(db):** 多驱动 SQLite 抽象层 — 新增 `SqliteAdapter` 接口及 3 个具体适配器(`betterSqliteAdapter`、`nodeSqliteAdapter`、`sqljsAdapter`),以及级联 `better-sqlite3` → `node:sqlite` → `sql.js (WASM)` 的 `driverFactory`。使 OmniRoute 能够在任何 JavaScript 运行时(Node.js、Bun、Deno、Cloudflare Workers)上运行,无需原生二进制依赖。`better-sqlite3` 已移至 `optionalDependencies`。([#2447](https://github.com/diegosouzapw/OmniRoute/pull/2447)) +- **feat(settings):** 设置 › AI 中的 Claude 快速模式开关 — 可选开关,转发 `X-CPA-Force-Fast-Mode` 头,使配对的 CLIProxyAPI 构建能够访问 Anthropic 快速模式(`speed:"fast"`)。模型门控至 Opus 模型,匹配 Anthropic 的二进制 KT() 检查。([#2449](https://github.com/diegosouzapw/OmniRoute/pull/2449) — 感谢 @NomenAK) +- **feat(settings):** Codex 快速等级 — 等级下拉菜单(`默认`/`priority`/`flex`)+ 逐模型门控,防止在非快速模式兼容模型上开启等级开关时 OpenAI 返回 400 错误。([#2451](https://github.com/diegosouzapw/OmniRoute/pull/2451) — 感谢 @NomenAK) +- **feat:** 适配 Antigravity 2.0.1 支持 — 更新客户端配置文件、上游头及模型别名。([#2443](https://github.com/diegosouzapw/OmniRoute/pull/2443) — 感谢 @dhaern) +- **feat:** 增强 `extractBearer` 以支持 `x-api-key`,用于 Anthropic API 风格的认证。([#2436](https://github.com/diegosouzapw/OmniRoute/pull/2436) — 感谢 @thedtvn) +- **feat(memory):** 将 `createMemory` 对接至 `upsertSemanticMemoryPoint`(Qdrant)。([#2439](https://github.com/diegosouzapw/OmniRoute/pull/2439) — 感谢 @NomenAK) -### 🔧 Bug Fixes & Refactors +### 🔧 问题修复 & 重构 -- **fix(deepseek-web):** rewrite auth to userToken Bearer + WASM PoW solver. ([#2452](https://github.com/diegosouzapw/OmniRoute/pull/2452) — thanks @ovehbe) -- **chore:** update node dependencies and runtime support. ([#2453](https://github.com/diegosouzapw/OmniRoute/pull/2453) — thanks @backryun) -- **fix(translator):** fix 3 Kiro `tool_result` defects causing 400 on follow-up turns — missing `tool_use_id` mapping, orphan result blocks, and conversation ID collision on assistant-first turns. ([#2447](https://github.com/diegosouzapw/OmniRoute/pull/2447)) -- **fix(translator):** treat `developer` role as system in OpenAI → Claude translation — `openAIToClaude` now extracts `developer`-role messages into `systemParts` (same as `system`) and filters them from the non-system message list, preventing identity context injected via the Responses API `developer` role from silently becoming an assistant turn when routing to a Claude-format provider. ([#2407](https://github.com/diegosouzapw/OmniRoute/issues/2407)) -- **fix(antigravity):** deduplicate `removeHeaderCaseInsensitive` — export canonical implementation from `antigravityClientProfile.ts` and remove the local copy in `antigravity.ts`; export `AntigravityCredentialsLike` type for cross-module use. (#2433 — thanks @Gi99lin) -- **refactor(docs):** enhance frontmatter handling in DocPage — gray-matter Date object parsing bug fix. ([#2448](https://github.com/diegosouzapw/OmniRoute/pull/2448) — thanks @ovehbe) -- **fix(jules):** Jules API parity and cloud-agent provider registration. ([#2438](https://github.com/diegosouzapw/OmniRoute/pull/2438)) -- **fix(i18n):** harden diff key extraction tag sanitization in `extract-keys-from-diff.mjs`. -- **chore(i18n):** refresh fr/es/de locales + add missing `settings.update` key. ([#2437](https://github.com/diegosouzapw/OmniRoute/pull/2437)) -- **fix(dashboard):** allow bracketed combo names — align dashboard combo-name validator regex with the shared/server schema updated in PR #2354; names like `Claude [1m]` are now accepted in the create/edit form. ([#2458](https://github.com/diegosouzapw/OmniRoute/pull/2458) — thanks @congvc-dev) -- **docs(agentrouter):** recommend native provider as the simple path — guide now prefers the built-in AgentRouter provider instead of manual OpenAI-compatible configuration. ([#2429](https://github.com/diegosouzapw/OmniRoute/pull/2429) — thanks @leninejunior) -- **feat(settings):** surface Codex Fast Tier toggle in Settings › AI — companion UI toggle for the Codex Fast Tier feature. ([#2440](https://github.com/diegosouzapw/OmniRoute/pull/2440) — thanks @NomenAK) +- **fix(deepseek-web):** 将认证重写为 userToken Bearer + WASM PoW 求解器。([#2452](https://github.com/diegosouzapw/OmniRoute/pull/2452) — 感谢 @ovehbe) +- **chore:** 更新 Node 依赖和运行时支持。([#2453](https://github.com/diegosouzapw/OmniRoute/pull/2453) — 感谢 @backryun) +- **fix(translator):** 修复 3 个 Kiro `tool_result` 缺陷,导致后续轮次返回 400 错误 — 缺失 `tool_use_id` 映射、孤立的结果块以及助手优先轮次中的会话 ID 冲突。([#2447](https://github.com/diegosouzapw/OmniRoute/pull/2447)) +- **fix(translator):** 在 OpenAI → Claude 翻译中将 `developer` 角色视为 system — `openAIToClaude` 现在将 `developer` 角色的消息提取到 `systemParts` 中(与 `system` 相同),并从非 system 消息列表中过滤掉它们,防止通过 Responses API `developer` 角色注入的身份上下文在路由到 Claude 格式服务商时静默变成助手轮次。([#2407](https://github.com/diegosouzapw/OmniRoute/issues/2407)) +- **fix(antigravity):** 去重 `removeHeaderCaseInsensitive` — 从 `antigravityClientProfile.ts` 导出规范实现并移除 `antigravity.ts` 中的本地副本;导出 `AntigravityCredentialsLike` 类型供跨模块使用。(#2433 — 感谢 @Gi99lin) +- **refactor(docs):** 增强 DocPage 中的 frontmatter 处理 — 修复 gray-matter Date 对象解析缺陷。([#2448](https://github.com/diegosouzapw/OmniRoute/pull/2448) — 感谢 @ovehbe) +- **fix(jules):** Jules API 对齐及云代理服务商注册。([#2438](https://github.com/diegosouzapw/OmniRoute/pull/2438)) +- **fix(i18n):** 加固 `extract-keys-from-diff.mjs` 中的差异键提取标签清理。 +- **chore(i18n):** 刷新法语/西班牙语/德语语言文件 + 添加缺失的 `settings.update` 键。([#2437](https://github.com/diegosouzapw/OmniRoute/pull/2437)) +- **fix(dashboard):** 允许带方括号的组合名称 — 将控制台组合名称校验正则与 PR #2354 中更新的共享/服务器 Schema 对齐;像 `Claude [1m]` 这样的名称现在可在创建/编辑表单中接受。([#2458](https://github.com/diegosouzapw/OmniRoute/pull/2458) — 感谢 @congvc-dev) +- **docs(agentrouter):** 推荐使用原生服务商作为简单路径 — 指南现在优先使用内置的 AgentRouter 服务商,而非手动 OpenAI 兼容配置。([#2429](https://github.com/diegosouzapw/OmniRoute/pull/2429) — 感谢 @leninejunior) +- **feat(settings):** 在 设置 › AI 中展示 Codex 快速等级开关 — Codex 快速等级功能的配套 UI 开关。([#2440](https://github.com/diegosouzapw/OmniRoute/pull/2440) — 感谢 @NomenAK) -### 🔒 Security Fixes +### 🔒 安全修复 -- **fix(security):** replace `execSync` string-template with `spawnSync` arg-array in `plugin.mjs` — eliminates shell command injection via malicious plugin names. -- **fix(security):** gate Electron CSP `unsafe-eval` on `!app.isPackaged` instead of URL substring match — was leaking `unsafe-eval` into production builds; merged duplicate `connect-src` directives. -- **fix(api):** add `requireManagementAuth` to `/api/usage/budget/bulk` and `/api/resilience/reset` — both endpoints exposed spend data and circuit-breaker controls without auth. -- **fix(security):** route catch-block error messages through `sanitizeErrorMessage()` in `gemini-web`, `claude-web`, `copilot-web` executors, `oauth` route, and cloud-agent task routes — prevents stack traces and internal paths leaking into HTTP responses. -- **fix(codex):** `refreshCredentials` returns `null` (not error-object) on token refresh failure — prevents base executor from spreading `{error}` onto active credentials. -- **fix(tokenRefresh):** safe `unknown`-error access in `catch` block (`error instanceof Error ? error.message : String(error)`). -- **fix(combo):** reset `exhaustedProviders` set at start of each set-retry iteration — providers excluded in a failing pass now get a second chance on retry. -- **fix(circuitBreaker):** persist and restore `lastFailureKind` via the `options` JSON column — kind-based cooldown overrides (`cooldownByKind`) now survive server restarts. +- **fix(security):** 在 `plugin.mjs` 中将 `execSync` 字符串模板替换为 `spawnSync` 参数数组 — 消除通过恶意插件名称进行的 shell 命令注入。 +- **fix(security):** 将 Electron CSP `unsafe-eval` 的门控条件从 URL 子串匹配改为 `!app.isPackaged` — 此前会将 `unsafe-eval` 泄露到生产构建中;合并了重复的 `connect-src` 指令。 +- **fix(api):** 为 `/api/usage/budget/bulk` 和 `/api/resilience/reset` 添加 `requireManagementAuth` — 这两个端点此前在无认证的情况下暴露了消费数据和熔断器控制。 +- **fix(security):** 在 `gemini-web`、`claude-web`、`copilot-web` 执行器、`oauth` 路由及云代理任务路由中,将 catch 块中的错误消息通过 `sanitizeErrorMessage()` 处理 — 防止堆栈跟踪和内部路径泄露到 HTTP 响应中。 +- **fix(codex):** 令牌刷新失败时 `refreshCredentials` 返回 `null`(而非错误对象)— 防止基础执行器将 `{错误}` 展开到活跃凭证上。 +- **fix(tokenRefresh):** 在 `catch` 块中安全访问 `unknown` 错误(`error instanceof Error ? error.message : String(error)`)。 +- **fix(combo):** 在每次集合重试迭代开始时重置 `exhaustedProviders` 集合 — 在失败轮次中被排除的服务商现在可在重试时获得第二次机会。 +- **fix(circuitBreaker):** 通过 `options` JSON 列持久化和恢复 `lastFailureKind` — 基于故障类型的冷却覆盖(`cooldownByKind`)现在可在服务器重启后保持。 --- ## [3.8.0] — 2026-05-06 -### 🚀 Post-release hotfixes e contribuições (2026-05-06 → 2026-05-20) +### 🚀 发布后热修复与贡献 (2026-05-06 → 2026-05-20) #### 2026-05-20 -- **feat(batch):** implement 10 feature requests harvested from issues — T3 Chat Web executor (cookie-based), per-request exhausted-provider tracking (#1731) to skip quota-drained providers mid-combo, Zed Docker detection, API key rotator health dashboard, Kiro multi-account isolation, context-window model filtering, cost blending in combos, combo config tests, provider validation branches, and postinstall support scripts. ([#2414](https://github.com/diegosouzapw/OmniRoute/pull/2414)) -- **feat(combos):** add `falloverBeforeRetry` strategy — combo routing now falls over to the next target before retrying the same model, eliminating the tail-latency spike from exhausting all per-model retries on a failing endpoint. Also wraps the retry loop in a `setTry` outer loop for per-target retry coordination. ([#2417](https://github.com/diegosouzapw/OmniRoute/pull/2417) — thanks @hartmark) -- **fix(gamification):** resolve 6 implementation gaps — missing `SELECT` in `checkActionCountBadges` SQL (was silently skipping 8 badges), federation leaderboard auth enforcement, pagination `offset` parameter no longer silently discarded, admin anomaly view now computes real z-scores, `addXp` correctly calculates initial level from XP amount, and barrel `index.ts` for clean module exports. 72-test suite covering all fixes. ([#2421](https://github.com/diegosouzapw/OmniRoute/pull/2421) — thanks @oyi77) -- **docs:** add AgentRouter provider setup guide — step-by-step instructions for connecting OmniRoute to AgentRouter.org's Claude-compatible relay endpoint, covering API key configuration and wire-image headers. ([#2422](https://github.com/diegosouzapw/OmniRoute/pull/2422) — thanks @leninejunior) +- **feat(batch):** 实现从 issue 中收集的 10 个特性请求 — T3 Chat Web 执行器(基于 cookie)、按请求追踪已耗尽服务商 (#1731) 以在 combo 中途跳过配额耗尽的服务商、Zed Docker 检测、API key 轮换健康仪表盘、Kiro 多账户隔离、上下文窗口模型过滤、combo 成本混合、combo 配置测试、服务商验证分支和 postinstall 支持脚本。([#2414](https://github.com/diegosouzapw/OmniRoute/pull/2414)) +- **feat(combos):** add `falloverBeforeRetry` strategy — Combo 路由 now falls over to the next target before retrying the same 模型, eliminating the tail-latency spike from exhausting all per-模型 重试 on a failing 端点. Also wraps the 重试 loop in a `setTry` outer loop for per-target 重试 coordination. ([#2417](https://github.com/diegosouzapw/OmniRoute/pull/2417) — 感谢 @hartmark) +- **fix(gamification):** resolve 6 implementation gaps — missing `SELECT` in `checkActionCountBadges` SQL (was silently skipping 8 badges), federation leaderboard 认证 enforcement, pagination `offset` parameter 不再 silently discarded, admin anomaly view now computes real z-scores, `addXp` correctly calculates initial level from XP amount, and barrel `index.ts` for clean module exports. 72-test suite covering all fixes. ([#2421](https://github.com/diegosouzapw/OmniRoute/pull/2421) — 感谢 @oyi77) +- **docs:** add AgentRouter provider setup guide — step-by-step instructions for connecting OmniRoute to AgentRouter.org's Claude-compatible 中继 端点, covering API Key 配置 and wire-image headers. ([#2422](https://github.com/diegosouzapw/OmniRoute/pull/2422) — 感谢 @leninejunior) - **fix(claude):** drop orphan `tool_result` blocks left behind when `fixToolAdjacency` strips a dangling `tool_use` — resolves HTTP 400 "unexpected tool_use_id in tool_result blocks" from the Anthropic API on truncated histories. `fixToolPairs` now re-runs after every `fixToolAdjacency` pass across all three call sites (`contextManager.ts`, `base.ts`, `claudeCodeCompatible.ts`). (discussion [#2410](https://github.com/diegosouzapw/OmniRoute/discussions/2410)) - **fix(playground):** guard against `null`/non-string model IDs in Playground dropdowns — `typeof m?.id !== "string"` check prevents a silent crash in the provider discovery loop and `filteredModels` computation that was leaving all Playground dropdowns empty when `/v1/models` returned entries with `id: null`; adds deduplication via `Set` to eliminate duplicate React key warnings. -- **fix(mitm):** point MITM runtime manager re-export to the compiled `.js` entrypoint — fixes module resolution after build when the `.ts` source is no longer present. -- **fix(storage):** persist `STORAGE_ENCRYPTION_KEY` across upgrades (closes #1622) — ensures that SQLite encryption keys are preserved during version upgrades. ([#2428](https://github.com/diegosouzapw/OmniRoute/pull/2428) — thanks @Chewji9875) -- **fix(auth):** auto-reset credential `apiKeyHealth` status on successful connection test. ([#2427](https://github.com/diegosouzapw/OmniRoute/pull/2427) — thanks @clousky2020) -- **fix(mitm):** drop `.js` extension on `manager.runtime` re-export to fix webpack packaging issue. ([#2425](https://github.com/diegosouzapw/OmniRoute/pull/2425) — thanks @NomenAK) -- **fix(image):** support Antigravity image generation and add Gemini 3.5 Flash support. ([#2423](https://github.com/diegosouzapw/OmniRoute/pull/2423) — thanks @backryun) +- **fix(mitm):** point MITM runtime manager re-export to the compiled `.js` entrypoint — fixes module resolution after build when the `.ts` source is 不再 present. +- **fix(storage):** 在版本升级期间持久化 `STORAGE_ENCRYPTION_KEY`(closes #1622)— 确保 SQLite 加密密钥在版本升级期间被保留。([#2428](https://github.com/diegosouzapw/OmniRoute/pull/2428) — 感谢 @Chewji9875) +- **fix(auth):** 连接测试成功后自动重置凭据 `apiKeyHealth` 状态。([#2427](https://github.com/diegosouzapw/OmniRoute/pull/2427) — 感谢 @clousky2020) +- **fix(mitm):** 移除 `manager.runtime` 重新导出中的 `.js` 扩展名以修复 webpack 打包问题。([#2425](https://github.com/diegosouzapw/OmniRoute/pull/2425) — 感谢 @NomenAK) +- **fix(image):** 支持 Antigravity 图像生成并新增 Gemini 3.5 Flash 支持。([#2423](https://github.com/diegosouzapw/OmniRoute/pull/2423) — 感谢 @backryun) #### 2026-05-19 -- **chore(i18n):** comprehensive dashboard i18n coverage — 6 rounds of parallel refactoring replacing hardcoded English/Portuguese text with `t()` calls across 57+ dashboard pages; 420+ new keys added to `en.json` covering `settings`, `playground`, `analytics`, `apiManager`, `providers`, `skills`, `memory`, `agents`, and 15 other namespaces (coverage: ~88%, up from ~20%). -- **fix(offline):** avoid SSR/CSR hydration mismatch on the offline status page — switches from a `useState` lazy initializer (which accessed `navigator.onLine` on the server) to `useSyncExternalStore` with a distinct `false` server snapshot, eliminating the React hydration warning. -- **fix(cli-tools):** guard `modelId` type before calling `.indexOf()` — prevents a `TypeError` when a model entry without a string `id` reaches the comparison logic in CLI Tools. -- **fix(providers):** add missing `isLocalProvider` import and update changelog. -- **fix(resilience):** add API Key health tracking with automatic rotation and UI toast alerts. ([#2412](https://github.com/diegosouzapw/OmniRoute/pull/2412) — thanks @clousky2020) -- **feat(providers):** support Gemini API keys for Gemini CLI executor. ([#2408](https://github.com/diegosouzapw/OmniRoute/pull/2408) — thanks @benzntech) -- **feat(gamification):** implement Gamification & Leaderboard System with non-blocking event-driven updates. ([#2405](https://github.com/diegosouzapw/OmniRoute/pull/2405) — thanks @oyi77) -- **fix(providers):** Kilo Code provider no longer blocks on a missing local `kilocode` CLI binary — the provider uses OAuth device flow + direct HTTPS to `api.kilo.ai` and never required the CLI at runtime; the connection test was hard-failing with "Local CLI runtime is not installed" even when the OAuth token was valid. CLI Tools integration (`/api/cli-tools/kilo-settings`) keeps its own runtime check. ([#2404](https://github.com/diegosouzapw/OmniRoute/issues/2404) — thanks @Flexible78) -- **fix(db):** `bun add -g omniroute` (and other runtimes that skip postinstall) no longer surfaces a generic 500 — `isNativeSqliteLoadError` now also detects "Could not locate the bindings file" / `MODULE_NOT_FOUND`, so the user gets the friendly rebuild guide instead. ([#2358](https://github.com/diegosouzapw/OmniRoute/issues/2358) — thanks @yamansin) -- **fix(kiro):** enable Google OAuth login option in the Kiro auth modal — surfaces the Google SSO button alongside the existing identity providers. ([#2392](https://github.com/diegosouzapw/OmniRoute/pull/2392) — thanks @congvc-dev) +- **chore(i18n):** 全面覆盖控制台 i18n — 经过 6 轮并行重构,将 57+ 个控制台页面中的硬编码英文/葡萄牙文文本替换为 `t()` 调用;在 `en.json` 中新增 420+ 个 key,覆盖 `settings`、`playground`、`analytics`、`apiManager`、`providers`、`skills`、`memory`、`agents` 及 15 个其他命名空间(覆盖率:约 88%,从约 20% 提升)。 +- **fix(offline):** 避免离线状态页面的 SSR/CSR 注水不匹配 — 从 `useState` 惰性初始化器(在服务器上访问 `navigator.onLine`)切换为 `useSyncExternalStore`,并使用独立的 `false` 服务器快照,消除了 React 注水警告。 +- **fix(cli-tools):** 调用 `.indexOf()` 前校验 `modelId` 类型 — 防止没有字符串 `id` 的模型条目到达 CLI 工具的比较逻辑时抛出 `TypeError`。 +- **fix(providers):** 添加缺失的 `isLocalProvider` 导入并更新变更日志。 +- **fix(resilience):** 新增 API Key 健康追踪功能,支持自动轮换和 UI 通知提示。([#2412](https://github.com/diegosouzapw/OmniRoute/pull/2412) — 感谢 @clousky2020) +- **feat(providers):** support Gemini API keys for Gemini CLI executor. ([#2408](https://github.com/diegosouzapw/OmniRoute/pull/2408) — 感谢 @benzntech) +- **feat(gamification):** implement Gamification & Leaderboard System with non-blocking event-driven updates. ([#2405](https://github.com/diegosouzapw/OmniRoute/pull/2405) — 感谢 @oyi77) +- **fix(providers):** Kilo Code provider no longer blocks on a missing local `kilocode` CLI binary — the 服务商 uses OAuth device flow + direct HTTPS to `api.kilo.ai` and never required the CLI at runtime; the 连接 test was hard-failing with "Local CLI runtime is not installed" even when the OAuth token was valid. CLI Tools 集成 (`/api/cli-tools/kilo-settings`) keeps its own runtime check. ([#2404](https://github.com/diegosouzapw/OmniRoute/issues/2404) — 感谢 @Flexible78) +- **fix(db):** `bun add -g omniroute` (and other runtimes that skip postinstall) no longer surfaces a generic 500 — `isNativeSqliteLoadError` now also detects "Could not locate the bindings file" / `MODULE_NOT_FOUND`, so the user gets the friendly rebuild guide instead. ([#2358](https://github.com/diegosouzapw/OmniRoute/issues/2358) — 感谢 @yamansin) +- **fix(kiro):** enable Google OAuth login option in the Kiro auth modal — surfaces the Google SSO 按钮 alongside the existing identity 服务商. ([#2392](https://github.com/diegosouzapw/OmniRoute/pull/2392) — 感谢 @congvc-dev) - **fix(security):** drop hashing layer in `sessionPoolKey` after switching to a non-cryptographic key derivation strategy that clears CodeQL alert #247. ([#2396](https://github.com/diegosouzapw/OmniRoute/pull/2396)) -- **feat(providers):** Gemini Web cookie-based provider — proxies google.com chat through a session cookie, allowing free Gemini access without API keys. ([#2380](https://github.com/diegosouzapw/OmniRoute/pull/2380) — thanks @oyi77) -- **model:** add Composer 2.5 to the Cursor provider catalog. ([#2381](https://github.com/diegosouzapw/OmniRoute/pull/2381) — thanks @backryun) -- **fix:** `tool_use` without adjacent `tool_result` causes Claude 400 — adjacency guard now also applies inside `compressContext`. ([#2383](https://github.com/diegosouzapw/OmniRoute/pull/2383) — thanks @oyi77) +- **feat(providers):** Gemini Web cookie-based provider — proxies google.com chat through a 会话 cookie, allowing free Gemini access without API keys. ([#2380](https://github.com/diegosouzapw/OmniRoute/pull/2380) — 感谢 @oyi77) +- **model:** add Composer 2.5 to the Cursor provider catalog. ([#2381](https://github.com/diegosouzapw/OmniRoute/pull/2381) — 感谢 @backryun) +- **fix:** `tool_use` without adjacent `tool_result` causes Claude 400 — adjacency 守卫 now also applies inside `compressContext`. ([#2383](https://github.com/diegosouzapw/OmniRoute/pull/2383) — 感谢 @oyi77) - **build(deps):** bump `electron` from 42.0.1 to 42.1.0 in `/electron`. ([#2397](https://github.com/diegosouzapw/OmniRoute/pull/2397)) - **build(deps):** production group bumps — 4 updates. ([#2398](https://github.com/diegosouzapw/OmniRoute/pull/2398)) - **build(deps):** development group bumps — 4 updates. ([#2399](https://github.com/diegosouzapw/OmniRoute/pull/2399)) @@ -2245,56 +2246,56 @@ must be a response to a preceding message with 'tool_calls'` when a Codex - **docs(readme):** restore the 9router acknowledgment that was inadvertently dropped during the v3.8.0 README rework. ([#2393](https://github.com/diegosouzapw/OmniRoute/pull/2393)) - **refactor(dashboard):** comprehensive nav, providers, endpoint, runtime, quota, pricing, budget redesign + quota sharing preview (sidebar restructure → 12 collapsible sections, 22 new routes). ([#2384](https://github.com/diegosouzapw/OmniRoute/pull/2384)) - **fix(dashboard):** PR #2384 follow-up review fixes — Runtime quota i18n, Budget projection logic, QuotaShare i18n externalization, Provider Limits semantic markup, bulk endpoint usage. ([#2389](https://github.com/diegosouzapw/OmniRoute/pull/2389)) -- **feat(content):** add Haiper, Leonardo, Ideogram, Suno, and Udio as content/media providers. ([#2377](https://github.com/diegosouzapw/OmniRoute/pull/2377) — thanks @oyi77) -- **feat(@omniroute/opencode-provider):** expand config helpers, add MCP entry, live model fetch, and combo builder. ([#2375](https://github.com/diegosouzapw/OmniRoute/pull/2375) — thanks @mrmm) -- **fix(claude-oauth):** enable system-transforms pipeline for the native Claude executor (closes 400 billing-gate). ([#2370](https://github.com/diegosouzapw/OmniRoute/pull/2370) — thanks @thepigdestroyer) -- **feat(content):** extend providers with video, audio, TTS, music capabilities — Pollinations, MiniMax, Together, Replicate across Audio TTS and Transcription registries. ([#2369](https://github.com/diegosouzapw/OmniRoute/pull/2369) — thanks @oyi77) -- **feat(providers):** add Veo AI Free as a web-wrapper provider for generating video, image, and TTS without an API key. ([#2366](https://github.com/diegosouzapw/OmniRoute/pull/2366) — thanks @oyi77) -- **feat(providers):** add Replicate as a free provider for OpenAI-compatible inference with community models. ([#2364](https://github.com/diegosouzapw/OmniRoute/pull/2364) — thanks @oyi77) -- **fix(claude):** avoided redundant deep cloning of Claude Code messages during semantic passthrough preparation, improving memory/CPU efficiency for large histories. ([#2362](https://github.com/diegosouzapw/OmniRoute/pull/2362) — thanks @terence71-glitch) +- **feat(content):** add Haiper, Leonardo, Ideogram, Suno, and Udio as content/media providers. ([#2377](https://github.com/diegosouzapw/OmniRoute/pull/2377) — 感谢 @oyi77) +- **feat(@omniroute/opencode-provider):** expand config helpers, add MCP entry, live model fetch, and combo builder. ([#2375](https://github.com/diegosouzapw/OmniRoute/pull/2375) — 感谢 @mrmm) +- **fix(claude-oauth):** enable system-transforms pipeline for the native Claude executor (closes 400 billing-gate). ([#2370](https://github.com/diegosouzapw/OmniRoute/pull/2370) — 感谢 @thepigdestroyer) +- **feat(content):** extend providers with video, audio, TTS, music capabilities — Pollinations, MiniMax, Together, Replicate across Audio TTS and Transcription registries. ([#2369](https://github.com/diegosouzapw/OmniRoute/pull/2369) — 感谢 @oyi77) +- **feat(providers):** add Veo AI Free as a web-wrapper provider for generating video, image, and TTS without an API key. ([#2366](https://github.com/diegosouzapw/OmniRoute/pull/2366) — 感谢 @oyi77) +- **feat(providers):** add Replicate as a free provider for OpenAI-compatible inference with community models. ([#2364](https://github.com/diegosouzapw/OmniRoute/pull/2364) — 感谢 @oyi77) +- **fix(claude):** avoided redundant deep cloning of Claude Code messages during semantic passthrough preparation, improving memory/CPU efficiency for large histories. ([#2362](https://github.com/diegosouzapw/OmniRoute/pull/2362) — 感谢 @terence71-glitch) - **fix(providers):** register `llm7` in the executor registry and route Cohere via OpenAI-compatible layer. ([#2361](https://github.com/diegosouzapw/OmniRoute/pull/2361), [#2360](https://github.com/diegosouzapw/OmniRoute/pull/2360)) - **fix(rate-limiter):** Redis is now opt-in — when `REDIS_URL` is unset, the rate limiter falls back to the in-memory store instead of spamming `ECONNREFUSED`. ([#2357](https://github.com/diegosouzapw/OmniRoute/pull/2357)) -- **fix(streaming):** emit protocol-aware stream errors — `createDisconnectAwareStream()` now emits native Responses API or Claude API SSE error blocks based on the client protocol. ([#2355](https://github.com/diegosouzapw/OmniRoute/pull/2355) — thanks @dhaern) -- **fix(combos):** allow bracketed combo names (e.g. `Claude [1m]`) by updating validation schemas. ([#2354](https://github.com/diegosouzapw/OmniRoute/pull/2354) — thanks @congvc-dev) -- **fix(claude-code):** semantic passthrough — preserve Claude Code `messages[]` structure for native Claude OAuth and relay routes. ([#2351](https://github.com/diegosouzapw/OmniRoute/pull/2351) — thanks @terence71-glitch) -- **fix(usage):** extract flat `cached_tokens` and `reasoning_tokens` from OpenAI-compatible usage objects. ([#2350](https://github.com/diegosouzapw/OmniRoute/pull/2350) — thanks @TF0rd) -- **fix(translator):** DeepSeek tool-call response lookup reads cached reasoning before falling back to empty string. ([#2349](https://github.com/diegosouzapw/OmniRoute/pull/2349) — thanks @herjarsa) -- **fix(ui/tooltip):** render in portal + clamp to viewport so tooltips aren't clipped in modal dialogs. ([#2352](https://github.com/diegosouzapw/OmniRoute/pull/2352) — thanks @slider23) +- **fix(streaming):** emit protocol-aware stream errors — `createDisconnectAwareStream()` now emits native Responses API or Claude API SSE error blocks based on the client protocol. ([#2355](https://github.com/diegosouzapw/OmniRoute/pull/2355) — 感谢 @dhaern) +- **fix(combos):** allow bracketed combo names (e.g. `Claude [1m]`) by updating validation schemas. ([#2354](https://github.com/diegosouzapw/OmniRoute/pull/2354) — 感谢 @congvc-dev) +- **fix(claude-code):** semantic passthrough — preserve Claude Code `messages[]` structure for native Claude OAuth and 中继 routes. ([#2351](https://github.com/diegosouzapw/OmniRoute/pull/2351) — 感谢 @terence71-glitch) +- **fix(usage):** extract flat `cached_tokens` and `reasoning_tokens` from OpenAI-compatible usage objects. ([#2350](https://github.com/diegosouzapw/OmniRoute/pull/2350) — 感谢 @TF0rd) +- **fix(translator):** DeepSeek tool-call response lookup reads cached reasoning before falling back to empty string. ([#2349](https://github.com/diegosouzapw/OmniRoute/pull/2349) — 感谢 @herjarsa) +- **fix(ui/tooltip):** render in portal + clamp to viewport so tooltips aren't clipped in modal dialogs. ([#2352](https://github.com/diegosouzapw/OmniRoute/pull/2352) — 感谢 @slider23) - **fix(auto-routing):** replace bare `getSettings()` with `getCachedSettings` to stop 500 on `auto/*` requests. ([#2346](https://github.com/diegosouzapw/OmniRoute/pull/2346)) - **fix(docker):** ship Dashboard Docs markdown in the container image. ([#2348](https://github.com/diegosouzapw/OmniRoute/pull/2348)) - **fix(combo/validator):** treat upstream responses carrying a non-empty `reasoning_content` as valid output. ([#2341](https://github.com/diegosouzapw/OmniRoute/pull/2341)) - **fix(account-fallback):** classify Anthropic `Usage Limit Reached` as `QUOTA_EXHAUSTED` with a 1h cooldown. ([#2321](https://github.com/diegosouzapw/OmniRoute/pull/2321)) -- **feat(providers):** add GitHub Models as a free provider — GPT-5, o-series, DeepSeek-R1, Llama 4, Grok 3. ([#2344](https://github.com/diegosouzapw/OmniRoute/pull/2344) — thanks @oyi77) -- **feat(providers):** add Hackclub AI as a free provider — 30+ models, no credit card required. ([#2339](https://github.com/diegosouzapw/OmniRoute/pull/2339) — thanks @oyi77) -- **feat(providers):** add Microsoft Copilot Web executor — WebSocket-based provider. ([#2340](https://github.com/diegosouzapw/OmniRoute/pull/2340) — thanks @oyi77) -- **feat(routing):** LKGP stores last known good account `connectionId` alongside provider. ([#2338](https://github.com/diegosouzapw/OmniRoute/pull/2338) — thanks @oyi77) +- **feat(providers):** add GitHub Models as a free provider — GPT-5, o-series, DeepSeek-R1, Llama 4, Grok 3. ([#2344](https://github.com/diegosouzapw/OmniRoute/pull/2344) — 感谢 @oyi77) +- **feat(providers):** add Hackclub AI as a free provider — 30+ models, no credit card required. ([#2339](https://github.com/diegosouzapw/OmniRoute/pull/2339) — 感谢 @oyi77) +- **feat(providers):** add Microsoft Copilot Web executor — WebSocket-based provider. ([#2340](https://github.com/diegosouzapw/OmniRoute/pull/2340) — 感谢 @oyi77) +- **feat(routing):** LKGP stores last known good account `connectionId` alongside provider. ([#2338](https://github.com/diegosouzapw/OmniRoute/pull/2338) — 感谢 @oyi77) - **feat(dashboard):** add Claude Code auth import/export UI + i18n (three-PR series: libs, API routes, dashboard UI). - **feat(dashboard):** add Gemini CLI auth import/export UI + i18n (three-PR series: libs, API routes, dashboard UI). - **fix(routing):** implement embedding combos, local provider validation bypass, and resolve migration collisions. - **fix(build):** import Monaco ESM API to fix webpack `nls.messages-loader` error. -- **fix(ui):** v3.8.0 polish — connections border, sticky tabs, EN translations, save toasts, auto-combo catalog. ([#2305](https://github.com/diegosouzapw/OmniRoute/pull/2305) — thanks @mrmm) -- **fix(auth+build):** Bearer manage scope on management routes + lazy-load deepseek PoW solver. ([#2308](https://github.com/diegosouzapw/OmniRoute/pull/2308) — thanks @mrmm) -- **fix(claude):** guard orphan tool_use/tool_result pairs before upstream send. ([#2312](https://github.com/diegosouzapw/OmniRoute/pull/2312) — thanks @mrmm) -- **fix(ui):** remove count from batch removal button. ([#2309](https://github.com/diegosouzapw/OmniRoute/pull/2309) — thanks @hartmark) -- **fix:** remove implicit API key request caps — removes default 1K/5K/20K rate caps. ([#2289](https://github.com/diegosouzapw/OmniRoute/pull/2289) — thanks @josephvoxone) -- **fix(sse):** strip stale `Content-Encoding`, `Content-Length`, and `Transfer-Encoding` headers on non-streaming forward. ([#2264](https://github.com/diegosouzapw/OmniRoute/pull/2264) — thanks @gleber) -- **chore(providers):** refresh provider model metadata and ordering. ([#2318](https://github.com/diegosouzapw/OmniRoute/pull/2318) — thanks @backryun) -- **chore(providers):** consolidate Alibaba provider entries. ([#2319](https://github.com/diegosouzapw/OmniRoute/pull/2319) — thanks @backryun) -- **fix(streaming):** harden stream readiness detection. ([#2317](https://github.com/diegosouzapw/OmniRoute/pull/2317) — thanks @dhaern) -- **fix(v1/messages):** default to non-streaming when `stream` field is absent for Anthropic format. ([#2326](https://github.com/diegosouzapw/OmniRoute/pull/2326) — thanks @thepigdestroyer) -- **fix(claude):** `fitThinkingToMaxTokens` caps thinking budget to model's output ceiling. ([#2327](https://github.com/diegosouzapw/OmniRoute/pull/2327) — thanks @thepigdestroyer) -- **fix(codex):** Codex reasoning priority resolves `modelEffort` before `explicitReasoning`. ([#2335](https://github.com/diegosouzapw/OmniRoute/pull/2335) — thanks @terence71-glitch) -- **fix(providers):** providers page no longer deadlocks when no providers are configured. ([#2329](https://github.com/diegosouzapw/OmniRoute/pull/2329) — thanks @slider23) -- **chore(providers):** update HuggingFace to use the new `/v1/` router endpoint. ([#2322](https://github.com/diegosouzapw/OmniRoute/pull/2322) — thanks @backryun) +- **fix(ui):** v3.8.0 polish — connections border, sticky tabs, EN translations, save toasts, auto-Combo catalog. ([#2305](https://github.com/diegosouzapw/OmniRoute/pull/2305) — 感谢 @mrmm) +- **fix(auth+build):** Bearer manage scope on management routes + lazy-load deepseek PoW solver. ([#2308](https://github.com/diegosouzapw/OmniRoute/pull/2308) — 感谢 @mrmm) +- **fix(claude):** guard orphan tool_use/tool_result pairs before upstream send. ([#2312](https://github.com/diegosouzapw/OmniRoute/pull/2312) — 感谢 @mrmm) +- **fix(ui):** remove count from batch removal button. ([#2309](https://github.com/diegosouzapw/OmniRoute/pull/2309) — 感谢 @hartmark) +- **fix:** remove implicit API key request caps — removes 默认 1K/5K/20K rate caps. ([#2289](https://github.com/diegosouzapw/OmniRoute/pull/2289) — 感谢 @josephvoxone) +- **fix(sse):** strip stale `Content-Encoding`, `Content-Length`, and `Transfer-Encoding` headers on non-streaming forward. ([#2264](https://github.com/diegosouzapw/OmniRoute/pull/2264) — 感谢 @gleber) +- **chore(providers):** refresh provider model metadata and ordering. ([#2318](https://github.com/diegosouzapw/OmniRoute/pull/2318) — 感谢 @backryun) +- **chore(providers):** consolidate Alibaba provider entries. ([#2319](https://github.com/diegosouzapw/OmniRoute/pull/2319) — 感谢 @backryun) +- **fix(streaming):** harden stream readiness detection. ([#2317](https://github.com/diegosouzapw/OmniRoute/pull/2317) — 感谢 @dhaern) +- **fix(v1/messages):** default to non-streaming when `stream` field is absent for Anthropic format. ([#2326](https://github.com/diegosouzapw/OmniRoute/pull/2326) — 感谢 @thepigdestroyer) +- **fix(claude):** `fitThinkingToMaxTokens` caps thinking budget to model's output ceiling. ([#2327](https://github.com/diegosouzapw/OmniRoute/pull/2327) — 感谢 @thepigdestroyer) +- **fix(codex):** Codex reasoning priority resolves `modelEffort` before `explicitReasoning`. ([#2335](https://github.com/diegosouzapw/OmniRoute/pull/2335) — 感谢 @terence71-glitch) +- **fix(providers):** providers page no longer deadlocks when no providers are configured. ([#2329](https://github.com/diegosouzapw/OmniRoute/pull/2329) — 感谢 @slider23) +- **chore(providers):** update HuggingFace to use the new `/v1/` router endpoint. ([#2322](https://github.com/diegosouzapw/OmniRoute/pull/2322) — 感谢 @backryun) - **fix(security):** resolve CodeQL ReDoS + URL sanitization alerts. - **fix(auth):** stop retrying unrecoverable token refresh failures and include connection id in token health check credentials. - **fix(auth):** return synthetic credentials for noAuth free providers and show no-auth card in dashboard instead of OAuth modal. - **fix(endpoint):** replace nested `