mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix(i18n): restore brand proper nouns and unify terminology in zh-CN and zh-TW (#8355)
* chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (#7168) * fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (#7179) * fix(ci): migrate Mergify auto-enqueue to merge_protections_settings.auto_merge_conditions (rules-based path is EOL 2026-07-16) (#7216) * fix(ci): drop Mergify batch settings (batching is a paid-tier feature; free plan queue is serial) (#7220) * fix(ci): merge queue tolerates the advisory dast-smoke failure (its GH-hosted build hang dequeued every attempt) (#7225) * test(ci): make the #6634 selfref guard hermetic — main's copy hard-fails every PR (#7341) main's copy of this test still does git I/O inside a unit test: const baseSrc = git(['show', 'origin/main:' + FILE]); Runners check out a shallow single ref, so origin/main does not resolve and the test dies with 'fatal: invalid object name origin/main'. Every PR into main fails Unit Tests (7/8) on it — today that is #7313, #7315, #7316, #7334, #7336 and #7337, six PRs red on a defect none of them introduced. #7313 has no other red at all. release/v3.8.49 already carries a fix (2e42b8efc, #7174: try/catch, fetch origin/main on demand, t.skip() when unreachable), but it only reaches main at release time — so main stays broken for the whole cycle. Cherry-picking it would also import a new problem: PR Test Policy classifies t.skip() as a silenced assertion, which we watched it correctly catch on #7300 today. This is the hermetic version instead (ported from #7327, which does the same for the release branch): read the file straight off disk, compare against an empty base so baseTaut/baseExtTaut are 0 — the strictest possible comparison point — and call evaluateMasking() directly. No git ref, no fetch, no skip, nothing the runner's checkout depth can break. The #6634 regression stays covered: the guard's logic lives in SELF_TEST_FIXTURE_RE (check-test-masking.mjs:337), not in the test. Proven both ways on main before committing — neutralise SELF_TEST_FIXTURE_RE to /$^/ and the test FAILS; restore it and it passes 2/2, with check-test-masking.mjs left byte-identical. Co-authored-by: growab <nekron@icloud.com> * chore(quality): tighten main's coverage baseline to the CI's real numbers (#7347) main's ratchet had been failing --require-tighten on every PR: 11 metrics improved but the baseline was never tightened. Same class as the #6634 selfref guard — an infra fix that lands only on the release branch leaves main red for the whole cycle, and every PR into main pays for it. Values are the merged-coverage numbers from a run on main itself (a local run measures ~68% vs CI's ~80%; the baseline's own note warns about that gap). Only the 11 coverage values change — gitleaks and semgrepFindings keep main's own state. No changelog fragment: #7326 carries it on release/v3.8.49, and a second one here would double the entry at release time. * Add cliproxy provider exposure controls and manifest injection (#7329) * feat(fusion): let judge use its own knowledge and override the panel (#6804) The judge prompt said to write an answer 'grounded in that analysis', implicitly capping output at the panel's union. When all panel members miss or are collectively wrong on something, the judge should apply its own reasoning as a full participant and override consensus, while keeping an honesty guard against fabrication. Adds a regression test. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> * fix(api): raise provider apiKey cap for cookie-based web providers (#6715) (#6759) * fix(cli): fall back to settings.json when Claude Code binary is unresolvable (#6701) (#6734) getCliRuntimeStatus() only ever answered `installed` from binary resolution (known install paths + where/which PATH search), so a stale PATH, moved binary, or uncatalogued install method reported "not found" even when ~/.claude/settings.json proved the CLI was installed and used before — regressing behind upstream 9router's checkClaudeInstalled(), which already falls back to the settings file when where/which fails. withSettingsFallback() (new src/shared/services/cliInstallFallback.ts, kept out of the frozen cliRuntime.ts to respect its file-size ceiling) restores that parity: only when the binary lookup's own reason is "not_found" (never for deliberate security rejections like unsafe/relative env overrides or symlink escapes) and the tool's settings file exists on disk. * fix(providers): honor explicit thinking.budget_tokens 0 in openai->gemini transform (#6813) (#6821) The transform forwarded the Claude-style thinking.budget_tokens into generationConfig.thinkingConfig.thinkingBudget, but the presence check was truthy (&& thinking.budget_tokens). An explicit budget_tokens: 0 — the natural way to disable thinking — is falsy, so it was dropped and the request fell through to the default thinkingConfig injection, making the model think despite an explicit request for zero. Use an explicit numeric check so 0 is honored as thinkingBudget 0; includeThoughts is only set for a non-zero budget. * fix(compression): reconcile outer vs per-engine token counts (#6488) (#6741) * fix(compression): reconcile outer vs per-engine token counts on degenerate output (#6488) Outer originalTokens/compressedTokens (real tiktoken counter over extracted message text) diverged from engineBreakdown[0]'s counts (a crude JSON.stringify(requestBody).length/4 estimate), worst on small/degenerate inputs where JSON structural overhead dominates. A single-engine breakdown entry represents the exact same before/after transformation as the overall response, so reconcileSingleEngineTokens() now overwrites that one entry's counts with the outer, more accurate figures; multi-step pipeline breakdowns are left untouched. * chore(6741): resolve release sync — CHANGELOG.md restored to release tip, entry moved to changelog.d fragment (fragments-first) * fix(api): accept enableRenderers in RTK compression config schema (#6703) (#6757) * fix(db): break probe-failed/restore loop on large storage.sqlite (#6632) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(cursor): add Opus 4.8, Fable 5, and Sonnet 5 model families (#6779) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's cursor registry + test changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(translator): read PDF/video file attachments for Gemini/Antigravity and Claude (#6790) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's translator + test changes. Co-authored-by: Wital <witalorocha216@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(codex): strip include from compact responses requests (#6805) * fix(codex): strip include from compact responses requests Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(6805): move include-strip assertion to standalone test file to keep executor-codex.test.ts under frozen size cap Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(i18n): translate hardcoded Portuguese dashboard strings to English (#6769) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(bootstrap): filter empty process.env values to prevent Docker env crash loop (#6828) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR) and the direct CHANGELOG.md edit (fragments-first); keeps only the author's bootstrap change. Co-authored-by: Andrian B. <andrewbalanesq@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): update SenseNova Token Plan support (#6330) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); the author's constants/registry/snapshot deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): classify 404 as MODEL_NOT_FOUND to stop retry storm (#6829) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR) and the direct CHANGELOG.md edit (fragments-first); the author's chatCore/errorClassifier deltas were re-applied cleanly onto the release tip. Co-authored-by: Andrian B. <andrewbalanesq@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(api): accept all catalog engines on compression PUT schema (#6792) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR). Resolved the release's OmniGlyph engine addition additively (types.ts/compression.ts kept both 'relevance' and 'omniglyph') and extended stackedPipelineStepSchema + STACKED_PIPELINE_ENGINE_INTENSITIES with the omniglyph branch so the ENGINE_CATALOG-parity test passes. Co-authored-by: Pitchfork-and-Torch <Pitchfork-and-Torch@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(api): point CLI health command at /api/monitoring/health (#6677) (#6717) * fix(api): point CLI health command at /api/monitoring/health (#6677) bin/cli/commands/health.mjs called GET /api/health, a route that was moved to /api/monitoring/health without updating the CLI; the top-level /api/health handler never existed on disk (only degradation/ and ping/ sub-routes). Point runHealthCommand()/runHealthComponentsCommand() at /api/monitoring/health and read its real payload shape (activeConnections, circuitBreakers: {open,halfOpen,closed}, memoryUsage) instead of the old nonexistent requests/breakers/cache/memory fields. * chore(6717): re-sync onto release tip; move CHANGELOG entry to changelog.d fragment (fragments-first) * chore(cursor): add Grok 4.5 effort/fast model IDs (#6774) Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): ensure DeepSeek Web SSE emits [DONE] after FINISHED (#6791) * fix(providers): ensure DeepSeek Web SSE emits [DONE] after FINISHED Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); keeps only the author's changes. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(deepseek): extract done-terminator helper to keep frozen file under cap Extracts the FINISHED-drain scheduler and finish-once guard added for the [DONE] terminator fix (#6777) into a new deepseek-web-done-terminator.ts module, so deepseek-web.ts stays under its frozen line cap (1148). Behavior is unchanged. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Pitchfork-and-Torch <Pitchfork-and-Torch@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(models): add capability override UI (#6727) * feat(models): add capability override UI Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); renumbered the migration 118 -> 119 to resolve the collision with 118_provider_param_filters.sql already on release/v3.8.47; the author's i18n/localDb deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(6727): import model-capability-overrides DB fns directly (not via localDb barrel) to keep localDb under file-size cap; aligns with anti-barrel convention * chore(db): satisfy known-symbols contract for modelCapabilityOverrides Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(cursor): use Agent CLI build id for x-cursor-client-version (#6795) * fix(cursor): use Agent CLI build id for x-cursor-client-version Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); the author's .env.example/docs deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): re-sync CHANGELOG.md to release tip (restore #6701 bullet) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(startup): rename reasoningControls.ts to avoid webpack casing collision (#6584) (#6718) * fix(startup): rename reasoningControls.ts to avoid webpack casing collision (#6584) * chore(6718): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(build): suppress Turbopack over-bundling warning from agentSkills generator (#6582) (#6720) * fix(build): suppress Turbopack over-bundling warning from agentSkills generator (#6582) generator.ts builds outputBase from a non-literal outputDir parameter, so Turbopack's file-tracing analyzer can't narrow it and emits an "Overly broad patterns" warning per entry point that imports the module (603 warnings on v3.8.46, up from 379). The fs access is legitimate and bounded, so next.config.mjs now suppresses this specific diagnostic via turbopack.ignoreIssue, mirroring the existing webpack.ignoreWarnings precedent in the same file. * chore(6720): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(providers): drop image_generation for Codex Spark models regardless of plan (#6651) (#6721) * fix(providers): drop image_generation for Codex Spark models regardless of plan (#6651) * chore(6721): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(providers): stop quota card re-sorting Codex/GLM bars by remaining % (#6687) (#6722) * fix(providers): stop quota card re-sorting Codex/GLM bars by remaining % (#6687) QuotaCardExpanded.tsx unconditionally re-sorted quotas by remaining percentage via sortQuotasByRemaining(), discarding the deterministic CODEX_QUOTA_ORDER/GLM_QUOTA_ORDER window order quotaParsing.ts's sortCodexOrder()/sortGlmOrder() had already established. A new hasFixedQuotaOrder() + resolveQuotaDisplayOrder() skip the re-sort for providers with a fixed window order (codex, glm family), threading providerId from QuotaCard.tsx through to the display layer. Regression guard: tests/unit/quota-card-expanded-fixed-order-6687.test.ts * chore(6722): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(startup): lazy-import ioredis in rateLimiter to fix MCP ERR_MODULE_NOT_FOUND (#6559) (#6725) * fix(startup): lazy-import ioredis in rateLimiter to fix MCP ERR_MODULE_NOT_FOUND (#6559) * chore(6725): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(resilience): resolve fp-pinned combo account back to real connection id (#6696) (#6732) * fix(resilience): resolve fp-pinned combo account back to real connection id (#6696) * chore(6732): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(api): Responses passthrough emits event-only SSE frames after filtering commentary output (#6561) (#6735) * fix(api): Responses passthrough emits event-only SSE frames after filtering commentary output (#6561) The #6199 commentary-drop `continue;` branches in stream.ts skipped the data: line for a dropped commentary event but never cleared the already-buffered event: line for the same frame, so the next blank line flushed the stale event: line alone -- an event-only SSE frame that crashes the OpenAI Python SDK's json.loads(). Both drop sites now call clearPendingPassthroughEvent() before continue. The commentary-drop decision was extracted into a new responsesCommentaryDrop.ts module so the fix does not grow the frozen stream.ts. * chore(6735): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(api): emit reasoning_content on claude-web + v0-vercel-web SSE (#6662) (#6743) * fix(api): emit reasoning_content on claude-web + v0-vercel-web /v1/chat/completions SSE (#6662) * chore(6743): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(sse): unwrap bare {function:{…}} tools in openai→claude translation (#6704) * fix(sse): unwrap bare {function:{…}} tools in openai→claude translation Some OpenAI-shape clients send a tool as a bare `{ function: {...} }` object, omitting the spec-required `type: "function"` parent wrapper. The tools-mapping in openai-to-claude.ts (~line 366) only unwrapped `tool.function` when `tool.type === "function"` was ALSO true, so a bare-function tool fell through to `toolData = tool` (the wrapper itself, with no `.name`), producing an empty `originalName` and silently dropping the tool from the translated request — worse than a 400, since the caller has no signal the tool never made it upstream. Unwrap `tool.function` whenever present, independent of the parent `type` field. Regression guard: tests/unit/openai-to-claude-bare-tool.test.ts. Co-authored-by: Samir Abis <me@samirabis.com> Inspired-by: https://github.com/decolua/9router/pull/2473 * chore(6704): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: Samir Abis <me@samirabis.com> * fix(oauth): avoid bare-email dedup of Codex OAuth logins (#6706) * fix(oauth): avoid bare-email dedup of Codex OAuth logins When an incoming Codex OAuth connection has no verifiable workspace/account id, do not merge it into an existing row on email match alone — that silently overwrote the other account's token pair. Require a matching chatgptUserId (a stable per-account JWT id) before merging; otherwise insert a distinct connection row. Co-authored-by: lucasjustinudin <34107354+lucasjustinudin@users.noreply.github.com> Inspired-by: https://github.com/decolua/9router/pull/2477 * chore(6706): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: lucasjustinudin <34107354+lucasjustinudin@users.noreply.github.com> * fix(sse): skip thinkingConfig for gemma models in openai→gemini translation (#6708) open-sse/translator/request/claude-to-gemini.ts already guards against sending thinkingConfig for gemma-4-* models (Gemma doesn't support it — Vertex returns 400: "Thinking budget is not supported for this model"), but the OpenAI-shape path (openai-to-gemini.ts) lacked the same guard, so OpenAI-shape clients hitting a vertex gemma-4-* model still got a 400. Mirrors the existing claude-to-gemini.ts guard: wrap the reasoning_effort and Claude-shape thinking.budget_tokens branches with a model.startsWith ("gemma-4") check. Branch 3 (default includeThoughts for modern Gemini models) already excludes non-"gemini" model ids and needed no change. Inspired-by: https://github.com/decolua/9router/pull/2480 Co-authored-by: chy1211 <31048289+chy1211@users.noreply.github.com> * fix(codex): surface capacity errors embedded in 200-OK SSE streams (#6710) * fix(codex): surface capacity errors embedded in 200-OK SSE streams Codex sometimes answers with HTTP 200 and a text/event-stream body whose payload carries a transient error mid-stream (e.g. "Selected model is at capacity...", server_is_overloaded, service_unavailable_error). Because the outer HTTP status was 200, this looked like a successful response to every caller — no retry, no circuit breaker, and no combo/account fallback ever engaged, so a healthy account sat idle while the request silently failed or truncated. Add peekCodexSseTransientError() to open-sse/executors/codex.ts: it peeks the first bytes of a text/event-stream Codex response, pattern-matches the known transient-error signatures, and converts a match into a real 503 Response via errorResponse() (Hard Rule #12 — sanitized, never raw upstream text). A 503 is already a recognized provider-failure status in accountFallback.ts, so combo routing and connection cooldown pick it up automatically. When no error signature is found, the peeked prefix is prepended back onto the remaining upstream body so the passthrough stays byte-identical to the unmodified response. Regression guard: tests/unit/codex-sse-capacity-fallback.test.ts — a model-at-capacity payload and a server_is_overloaded/service_unavailable_error payload both convert to 503; a normal single-chunk SSE stream and one split across multiple network chunks both reassemble byte-for-byte unchanged. Inspired-by: https://github.com/decolua/9router/pull/2452 (sub-bug #3 only — OmniRoute already covers PR #2452's other two sub-bugs: service_tier "fast" normalization and reasoning_effort "max" normalization). Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> * chore(6710): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> * fix(volcengine): clamp Kimi max_tokens to Ark endpoint cap (#6712) * fix(volcengine): clamp Kimi max_tokens to Ark endpoint cap VolcEngine Ark's Kimi coding-plan endpoint (ark.cn-beijing.volces.com) enforces max_tokens <= 32768 server-side and returns 400 "integer above maximum value, expected a value <= 32768" for anything over that ceiling. OmniRoute's StripRule only supported dropping params outright, with no numeric clamp mechanism, so a client sending a larger max_tokens (common default, e.g. 65536) 400s outright against volcengine's kimi-k2-5-260127. The 32768 cap is independently confirmed against two live-endpoint bug reports hitting this exact Ark endpoint for both kimi-k2.5 and kimi-k2.7-code (NousResearch/hermes-agent#51773, MoonshotAI/kimi-cli#1124), not just upstream's own value — same cap upstream 9router#2460 uses. StripRule gains two optional fields: `clampToModelMaxOutput` (clamp to the model's own catalog maxOutputTokens ceiling, when set) and `maxOutputCap` (a fixed endpoint-imposed ceiling); when both apply, the lower wins. The new rule is scoped to the literal id `kimi-k2-5-260127` (OmniRoute's real volcengine Kimi model, not upstream's `Kimi-K2.7-Code`), not a broad /kimi/i regex, so it can never clamp an unrelated future Kimi listing whose Ark cap may differ. glm-4-7-251222 (the other volcengine model) is unaffected. Inspired-by: https://github.com/decolua/9router/pull/2460 Co-authored-by: whale9820 <whale9820@users.noreply.github.com> * chore(6712): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: whale9820 <whale9820@users.noreply.github.com> * fix(antigravity): surface aborted Gemini tool calls off end_turn (#6713) * fix(antigravity): surface aborted Gemini tool calls off end_turn Gemini/Antigravity aborts a turn with finishReason MALFORMED_FUNCTION_CALL (or a sibling like UNEXPECTED_TOOL_CALL) instead of completing cleanly. Both Claude-facing translators collapsed these to a clean end_turn, hiding the aborted tool call as a successful completion: - the OpenAI hub path (openai-to-claude.ts convertFinishReason default), and - the DIRECT Gemini->Claude path (gemini-to-claude.ts), which is the one Claude Code actually hits through an antigravity/Gemini-routed model. Add isAbortFinishReason() to finishReason.ts and map these reasons to tool_use on both paths; genuinely unknown reasons still fall back to end_turn. Co-authored-by: anhdiepmmk <n08ni.dieppn@gmail.com> Inspired-by: https://github.com/decolua/9router/pull/2462 * chore(6713): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: anhdiepmmk <n08ni.dieppn@gmail.com> * fix(translator): strip empty cloud_base_branch from Cursor Subagent tool call (#6729) * fix(translator): strip empty cloud_base_branch from Cursor Subagent tool call (port from 9router#2446) The Responses->Chat tool-arg cleanup (stripEmptyOptionalToolArgs) only stripped empty-string/empty-array optional args for Claude Code's Read tool. Cursor's local Subagent tool call therefore passed through with the cloud-only field cloud_base_branch: "", which Cursor rejects ("cloud_base_branch may only be specified when environment equals cloud") before starting the subagent. Extend the cleanup to an allowlist of Read + Subagent; arbitrary tools stay untouched. Reported-by: like3213934360-lab (https://github.com/decolua/9router/issues/2446) * chore(6729): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) * fix(translator): defer content_block_start until GLM streams the tool name (#6730) * fix(translator): defer content_block_start until GLM streams the tool name (port from 9router#2077) GLM 5.2 (and similar OpenAI-compatible upstreams) stream a tool call's id and function.name across separate SSE delta chunks. The openai-to-claude streaming translator emitted content_block_start immediately on the id-only chunk with an empty name; the Claude SSE protocol cannot patch a block after emission, so the later name-only chunk was dropped and Claude Code rejected the tool_use with an empty tool name / "No such tool available:". Defer content_block_start until the name arrives (start on args if they arrive first), and emit a start for any orphaned id-only tool call at finish so content_block_stop is never orphaned. Reported-by: itiwant (https://github.com/decolua/9router/issues/2077) * chore(6730): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) * feat(dashboard): add search to Playground model picker dropdown (#4086) (#6811) * feat(dashboard): add search to Playground model picker dropdown (#4086) The shared ModelSelectModal (combo builder + CLI-code cards) already had search, but the Playground's raw model <select> in StudioConfigPane stayed a flat unsearchable list - unusable once a provider like OpenRouter contributed 50+ models. Adds a search input above the dropdown that filters options via filterModelsByQuery() (Turkish-safe accent/case-insensitive match, reusing matchesSearch()). The currently selected model always stays pinned in the list even when it doesn't match the query, so typing never silently swaps the active selection. Reuses the existing common.search i18n key already translated in all 42 locales - no new key needed. * chore(6811): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) * feat: request count log per provider, per date (#4009) (#6812) * feat(dashboard): request count log per provider, per date (#4009) Some providers bill by request rather than by token, so operators need a plain per-provider, per-date request count breakdown, not just token aggregates. Adds a new getProviderDailyUsageRows() aggregation query (src/lib/db/usageAnalytics.ts), a dedicated GET /api/usage/requests-by-provider-date route (kept separate from the frozen /api/usage/analytics route to respect the file-size baseline), and a sortable, single-date-filterable table on Dashboard -> Analytics. Closes #4009 * chore(6812): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) * feat(xai): route xAI clients to Grok native /v1/responses endpoint (#6709) * feat(xai): route xAI clients to Grok native /v1/responses endpoint xAI ships a native /v1/responses endpoint (https://api.x.ai/v1/responses) alongside /v1/chat/completions, but XaiExecutor extended BaseExecutor without overriding buildUrl(), so every request always resolved to the static chat-completions baseUrl regardless of target format — the last genuinely-missing slice of decolua/9router#2439 (grok-build-0.1, the reasoning-effort suffix routing, and bare grok-* routing were already ported in prior cycles). Add responsesBaseUrl to the xai registry entry and tag grok-4.20-multi-agent-0309 (upstream's own Responses-only id) with targetFormat: "openai-responses", mirroring the existing model-tag-driven routing pattern already used by the gh executor (9router#102) and the "openai" -pro heuristic in open-sse/executors/default.ts — the per-model registry tag is the single source of truth that also drives chatCore's body translation, so URL and body stay in lockstep. XaiExecutor.buildUrl now checks getModelTargetFormat("xai", model) and resolves to the native Responses endpoint only for tagged models, leaving every other grok-* model on the existing chat-completions bridge. TDD: tests/unit/executor-xai.test.ts adds a RED-then-GREEN case asserting grok-4.20-multi-agent-0309 resolves to https://api.x.ai/v1/responses and a control case asserting grok-4.3 still resolves to https://api.x.ai/v1/chat/completions. Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> Inspired-by: https://github.com/decolua/9router/pull/2439 * chore(6709): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> * fix(resilience): route remaining credential-selection call sites through quota preflight (#6686) (#6742) * fix(resilience): route remaining credential-selection call sites through quota preflight (#6686) * chore(6742): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(resilience): apikey-provider 429s honor explicit quota-exhausted text (#6638) (#6731) * fix(resilience): apikey-provider 429s honor explicit quota-exhausted text (#6638) Ollama Cloud (and any other apikey-category provider) 429s skipped body-text quota classification entirely; a genuine multi-day quota exhaustion was misclassified as a plain rate_limit_exceeded with a few seconds of cooldown, so combo routing retried the account immediately. shouldPreserveQuotaSignals() now lets an explicit quota-exhausted signal (looksLikeQuotaExhausted) override the apikey-category default, and parseDayGranularityResetMs() adds day- granularity reset-hint parsing ("...reset in 3 days.") alongside the existing Xh/Ym/Zs parsing. Regression guard: tests/unit/issue-6638-ollama-quota.test.ts (RED before the fix, GREEN after). Aligned two tests/unit/account-fallback-service.test.ts cases that had codified the old buggy behavior for apikey-provider quota text. * chore(6731): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * feat(resilience): weekly-429 cooldown for fetcher-less providers (#3709) (#6817) * feat(resilience): weekly-429 cooldown for fetcher-less providers (#3709) Ollama Cloud free-tier accounts have a hard WEEKLY request cap. On cap the upstream returns 429 "you (<account>) have reached your weekly usage limit", but ollama-cloud is an apikey-category provider, so the existing oauth-only shouldUseQuotaSignal gate in checkFallbackError skips the subscription-quota-text classifier (Issue #2321) for its 429s -- the account fell through to the generic exponential backoff (~1s, capped at 2min) and got retried every few minutes for the rest of the week (one account took 285x429 in 48h). Adds a new, ungated weekly-usage-limit text classifier that applies a 24h QUOTA_EXHAUSTED cooldown regardless of provider category. Extracted the new classifier -- together with the existing #2321 subscription-quota logic -- into a new open-sse/services/quotaTextCooldowns.ts module so the frozen accountFallback.ts (file-size-baseline cap) didn't have to grow; net effect shrinks accountFallback.ts by 20 lines. This is Phase A of the plan (open-sse/services/accountFallback.ts:1038-1045 "weekly-429 cooldown"); Phase B (generic local request-counter preflight for manual provider_plans dimensions) is a separate, larger follow-up per the plan's own phasing. * chore(6817): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * fix(providers): Kiro adaptive-thinking allowlist excludes sonnet-4.5/haiku-4.5 (#6576) (#6726) * fix(providers): Kiro adaptive-thinking allowlist excludes sonnet-4.5/haiku-4.5 (#6576) * chore(6726): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) * test(kiro): migrate selector-strip test to claude-sonnet-5 (only Kiro adaptive-thinking model, #6576) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): rebaseline complexity 2053->2054 (merge-burst drift, v3.8.47) Inherited drift from today's /implement-prs merge burst (~36 PRs). check:complexity does not run on the PR->release fast-path, so the branch accrued +1 unmeasured. No orphan/feature PR introduces a NEW violation (complexity-net-zero); the only flagged function is the pre-existing getResolvedModelCapabilities. Owner-approved rebaseline to unblock the FQG of ~7 green-except-complexity orphans. * chore(stryker): register ollama-quota covering tests (merge-burst drift, v3.8.47) The 3 covering unit tests from #6731/#6817/#6742 (issue-6638-ollama-quota, ollama-cloud-weekly-quota-cooldown-3709, issue-6686-quota-preflight-coverage) exist on release but were never added to tap.testFiles when those PRs merged. Completes the registration so mutant kills count; unblocks every PR touching a mutated module. Part of the owner-approved merge-burst drift cleanup. * fix: auto-start WS server in-process and change default port to 20132 (#6072) * feat: change default LIVE_WS_PORT from 20129 to 20132 Update the default WebSocket port for the live dashboard server from 20129 to 20132 across all configuration files, documentation, code comments, and tests. Also consolidate OMNIROUTE_DISABLE_LIVE_WS and OMNIROUTE_ENABLE_LIVE_WS into a single OMNIROUTE_ENABLE_LIVE_WS flag. Wire the live WebSocket server to start in-process via instrumentation-node.ts. * feat: clarify NEXT_PUBLIC_LIVE_WS_PUBLIC_URL path usage and derive upgrade path from URL Update .env.example and ENVIRONMENT.md to document that the pathname portion of NEXT_PUBLIC_LIVE_WS_PUBLIC_URL (e.g. /live-ws) is used as the WebSocket upgrade path by the dev proxy, handshake response, and client connection logic. Extract deriveLiveWsPath() into shared/utils/wsPath.ts and wire it through: - src/app/api/v1/ws/route.ts — handshake response path field - src/hooks/useLiveDashboard.ts — build * fix: use the standard URL API to safely parse and update the effectiveWsUrl * build(docker): expose live WebSocket server port and configure CORS origins Add LIVE_WS_PORT (20132), LIVE_WS_HOST (0.0.0.0), and LIVE_WS_ALLOWED_ORIGINS environment variables to all Docker Compose profiles and expose the WebSocket port mapping. Prevent infinite self-loop in standalone-server-ws.mjs by skipping proxy when the server itself is running on the LiveWS port. * docs(env): fix comment formatting for HOST and HOSTNAME variables --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(logs): prevent stale detail refresh reopening modal (#6323) * fix(logs): prevent stale detail refresh reopening modal * chore(stryker): register ollama-quota covering tests (release drift from merge burst) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * \ feat: operator-configurable account rotation\ (#6763) * feat(resilience): operator-configurable account rotation Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR); the author's accountFallback/.env deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(env): document configurable account-rotation env vars in ENVIRONMENT.md Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(rotation): extract rotation gate/context helpers to keep accountFallback.ts under frozen cap Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): re-sync CHANGELOG.md to release tip (restore lost base bullet) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(stryker): register rotation-config test in tap.testFiles for mutation coverage Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(stryker): register ollama-quota covering tests (drift from #6731/#6817/#6742) + re-sync Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> * fix(lmarena): modernize Arena web provider + static Direct-chat catalog (#6280) * fix(lmarena): modernize Arena web provider + static Direct-chat catalog Update the lmarena provider for arena.ai (product rebranded from LMArena): - Route chat via arena.ai create-evaluation with Chrome TLS impersonation (tls-client-node) and optional browser-minted recaptchaV3Token. - Seed Text+Search (48) into the chat registry; seed Image (27) only into IMAGE_PROVIDERS. Disable live HTML model discovery; resolve public names to Arena UUIDs from the static TypeScript allowlist (no scrape JSON in-repo). - Soft-exclude 404/502 model ids; slow/stop bulk test-all probes for this provider. - Do not fold IMAGE_PROVIDERS/video specialty into the chat provider catalog when a chat registry already exists (lmarena/openai/xai). - Display name Arena (Free); keep wire id `lmarena` / alias `lma` for back-compat. - Theme-aware provider icons: arena-light.svg / arena-dark.svg. - Preserve split Supabase SSR cookie reconstruction for arena-auth-prod-v1.*. * fix(providers): align provider-models-route test fixture + regen provider reference Fold the topaz image-only catalog entry's apiFormat/supportedEndpoints into the local-catalog test fixture (route now tags media-only providers per the lmarena PR's staticModels.ts change), regenerate PROVIDER_REFERENCE.md against the merged release providers.ts, and add the changelog fragment for #6280. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test: align web-cookie fallback suite — lmarena now has a registry entry (probe path) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(changelog): reconcile 3-day merge burst — 16 fragments, 4 promised credits, contributors hall 32→63 - changelog.d fragments for the 20 merged PRs that landed without a bullet (#6072 #6308 #6323 #6538 #6556 #6586 #6611 #6647 #6675 #6698 #6757 #6759 #6804 #6821 + ci rollup #6781/#6691/#6693 + docs rollup #6643/#6644/#6646/#6663; omniglyph bump #6661 folded into the #6556 bullet) - deliver the 4 credits promised in close comments but never written: @alltomatos (#6819 dup of #6721), @samimozcan (#6762/#6753 subsumed by #6790), @chirag127 (#6756 dup of #6757), @Squawk7777 (#6565 dup of #6564 — appended to the existing #6564 bullet; changelog-integrity flags that edit as a removal, intentional: ALLOW_CHANGELOG_REMOVALS justification) - rebuild the v3.8.47 Contributors hall from merged-PR authors + thanks credits + prior hall: 32 → 63 contributors * Clamp reasoning token buffer to model output cap (#6714) * fix(combo): clamp reasoning buffer to model output cap * fix(routing): preserve near-cap reasoning max tokens * fix(routing): getExplicitModelOutputCap falls through to registry cap on non-numeric synced limit_output getExplicitModelOutputCap short-circuited to null whenever a synced capability row existed, even if that row's limit_output was not a number (models.dev commonly omits it). That silently disabled the reasoning-token buffer clamp for any model with a synced row lacking an output limit. Now only return the synced value when it IS a number; otherwise fall through to registryModel.maxOutputTokens / spec.maxOutputTokens, matching the ??-chain precedence already used by getResolvedModelCapabilities(). Adds a standalone regression test (proves the fallthrough returns the real registry cap, not null) and hardens the #6274 fixture id so its no-output-cap case does not prefix-match the real glm-5.2 static spec. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(stryker): register ollama-quota covering tests (release drift from merge burst) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(i18n): add Traditional Chinese (zh-TW) localization for frontend and CLI (#6320) * feat(i18n): add Traditional Chinese (zh-TW) localization for frontend and CLI - Add src/i18n/messages/zh-TW.json translating frontend web UI - Add bin/cli/locales/zh-TW.json translating CLI commands and descriptors - Register zh-TW in config/i18n.json and docs/guides/I18N.md - Update scripts/i18n/generate-multilang.mjs matching the new locale setup * fix: update i18n locale count from 42 to 43 after adding zh-TW The docs strict checker (check-docs-counts-sync.mjs) validates that README.md and I18N.md reflect the real locale count. Adding zh-TW bumped the count from 42 → 43. * fix(i18n): translate providers free-filter labels in zh-TW (#6694 guard) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: lunkerchen <lunkerchen@users.noreply.github.com> * feat(proxy): implement latency-optimized proxy rotation strategy (#6798) * feat(proxy): implement latency-optimized proxy rotation strategy Reconstructed onto release/v3.8.47 to drop unrelated main-drift (deps/electron/proxy files belong to #6620, not this PR) and the direct CHANGELOG.md edit (fragments-first); the author's env/docs/i18n deltas were re-applied cleanly onto the release tip. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(proxy): add latency-rotation env var to .env.example PROXY_LATENCY_WINDOW_HOURS was referenced in src/lib/db/proxies.ts and documented in docs/reference/ENVIRONMENT.md, but missing from .env.example, tripping the env/docs sync gate. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): re-sync CHANGELOG.md to release tip Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(proxy): extract latency-strategy helpers to keep frozen files under cap Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * test(db-rules): expect 35 audited modules (proxyLatency joins INTENTIONALLY_INTERNAL) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs(readme): fix stale strategy/tool/scoring counts (#6853) README still claimed 17 routing strategies (the table was missing pipeline), 95 MCP tools, and 9-factor Auto-Combo scoring. Align with the source (ROUTING_STRATEGY_VALUES has 18 entries) and the canonical docs (MCP-SERVER.md: 94 tools; AUTO-COMBO.md: 12-factor). * fix(antigravity): sanitize Cloud Code safety settings (#6839) Co-authored-by: kfiramar <83420275+kfiramar@users.noreply.github.com> * fix(kiro): probe IdC region during profileArn discovery, cross-region (recovers #6099) (#6840) * fix(kiro): route Amazon Q runtime by profileArn region for cross-region IdC Enterprise AWS IAM Identity Center accounts whose IdC instance lives outside the two Amazon Q Developer profile regions (us-east-1 / eu-central-1) - e.g. eu-north-1 (Stockholm), start URL https://d-XXXX.awsapps.com/start - showed no limits and returned 502 on every request. Root cause: the backend used the IdC/OIDC token region (providerSpecificData.region, e.g. eu-north-1) for every CodeWhisperer runtime call, hitting q.eu-north-1.amazonaws.com - a host that does not exist as a Q Developer runtime endpoint. Per AWS docs ("Supported Regions for the Q Developer console and Q Developer profile"), the Q Developer *profile* (which produces the profileArn and hosts generateAssistantResponse / GetUsageLimits / ListAvailableModels / ListAvailableProfiles) is only hosted in us-east-1 and eu-central-1, regardless of the IdC region; "data is stored in the Region where you create the Amazon Q Developer profile." Fix (new open-sse/services/kiroRegion.ts) decouples the two regions: - providerSpecificData.region stays the IdC/OIDC region, used ONLY for oidc.{region}.amazonaws.com token mint/refresh. - The runtime region is derived from the profileArn (resolveKiroRuntimeRegion): profileArn region -> a valid stored profile region -> us-east-1. A stored IdC region that is not a Q profile region (eu-north-1) is ignored for runtime. - Profile discovery (discoverKiroProfileArnAcrossRegions) probes the Q profile regions (EU IdC -> eu-central-1 first) with the cross-region SSO token instead of q.{idcRegion}. Wired into: executors/kiro.ts (generateAssistantResponse targets the profile region), services/usage/kiro.ts (getKiroUsage multi-region discovery + profileArn runtime region so Limits resolves), services/kiroModels.ts (ListAvailableModels), and src/lib/oauth/providers/kiro.ts (login-time postExchange profile discovery). Adds tests/unit/kiro-idc-cross-region.test.ts (15 cases). All Kiro suites pass (60 tests). * fix(kiro): probe the IdC region too during profileArn discovery (any IdC region) Make profile discovery general for an IdC in ANY of the ~30 IdC-supported AWS regions (us-west-2, ap-southeast-2, me-central-1, af-south-1, ...), not just eu-north-1. buildKiroProfileDiscoveryRegions now probes the two documented Q Developer profile regions FIRST (us-east-1 / eu-central-1, EU-first for EMEA IdC regions to cut latency), then appends the IdC/stored region itself as a forward-compatible fallback: if AWS ever co-locates the profile with the IdC or expands the profile-region list, a same-region probe still finds it. Probing a region with no profile simply returns nothing and we fall through. The profileArn's own region remains authoritative for every runtime call (resolveKiroRuntimeRegion), so a newly-issued ARN in any region is honored automatically. Adds ap-southeast-2 (APAC) cross-region coverage and updates the discovery-order tests. --------- Co-authored-by: artickc <artur1992123@mail.ru> * feat(providers): manual context-window override for custom models (#4125) (#6822) Add a manual per-model "Context Window Override" so an operator can correct a provider's misreported context length (e.g. reports 1M when the real limit is 128K) instead of the model getting silently dropped from combo routing once the wrong value lands in the catalog. Reuses the existing Feature-5004 model_context_overrides table (source="manual") — already the priority-0 source getModelContextLimit() (the function combo's context-window filter calls) reads ahead of the models.dev/registry/static catalog — so no new resolver logic was needed, only the missing write path: - PUT /api/provider-models now accepts an optional contextWindowOverride (number to set, null to clear), persisted via setModelContextOverride/ removeModelContextOverride. - GET /api/provider-models surfaces the current override value + source back on each custom-model row. - CustomModelsSection.tsx: edit form gained a Context Window Override input + a badge on the model row when an override is set. Regression guard: tests/unit/provider-models-context-window-override-4125.test.ts (manual override wins over a misreported catalog value, GET round-trip, clearing via null, default-unchanged behavior). * feat(dashboard): improve Provider Quota page horizontal density (#3520) (#6815) QuotaCardGrid stacked every provider group vertically in a single flex flex-col container, and each group's own card grid didn't go multi-column until the md breakpoint. Provider groups now flow into a 2-column CSS multi-column layout on very wide (2xl) screens instead of an unconditional vertical stack, and each group's card grid starts at 2 columns immediately, filling horizontal whitespace sooner on narrower-but-not-mobile viewports. Regression guard: tests/unit/quota-card-grid-horizontal-layout.test.ts * refactor(usage): type saveRequestUsage with UsageEntry interface + any-budget ratchet (#3512) (#6809) Replace saveRequestUsage(entry: any) with a typed UsageEntry interface mirroring the usage_history columns 1:1. Fields stay optional/nullable since different writers (chatCore success/failure, rejected-request accounting, Codex Responses WS) populate the row incrementally; tokens stays unknown since callers pass either raw provider-shaped usage or the normalized {input,output,cacheRead,...} shape. Also cleaned the file's other any usages (getUsageHistory filter, getUsageDb next-cursor cast, appendRequestLog tokens param, getRecentLogs catch) so it now sits at zero any and can be added to the check:any-budget:t11 zero-any allowlist. Documents the DB-entity <-> TS-interface convention in docs/architecture/CODEBASE_DOCUMENTATION.md Sec 11. * feat(combo): strict budget-cap fallback policy for auto/* combos (#3470) (#6816) Auto-combo transparency + budget controls: the engine's budgetCap enforcement always degraded to the globally cheapest candidate when every candidate exceeded the cap - silently overspending instead of respecting the cap. - engine.ts: budgetFallback "cheapest" (default, legacy) | "strict" (BudgetExceededError when no candidate fits budgetCap) - requestControls.ts: X-OmniRoute-Budget-Fallback header + resolveRequestAutoControls() consolidating mode/budget/fallback parsing - resolveAutoStrategy.ts / autoConfig.ts: thread combo-level config.budgetFallback and catch BudgetExceededError into an HTTP 402 - chat.ts: switch to the consolidated resolveRequestAutoControls() helper (net line reduction, stays under the frozen file-size baseline) Regression guard: tests/unit/auto-combo-budget-fallback-3470.test.ts * fix(usage): honor xAI provider-reported exact cost (#6711) OmniRoute's calculateCost() always estimated request cost from token counts x static pricing, discarding xAI's exact provider-reported cost when present. xAI's chat-completions usage object reports the precise billed cost via cost_in_usd_ticks (docs.x.ai/developers/cost-tracking and the API reference's usage schema: "TICKS_IN_USD_CENT: i64 = 100_000_000" => 1e10 ticks/USD, e.g. 37756000 ticks ~= $0.0038). calculateCost()/computeCostFromPricing() now short-circuit to this exact figure when present -- before any pricing DB lookup, so it also works for models without a local pricing row -- and still fall back to the token-based estimate when it is absent. The field is threaded through both the streaming (extractUsage/normalizeUsage) and non-streaming (extractUsageFromResponse) usage-extraction paths. Corrected divisor vs upstream: the upstream PR used /1e12 (a 100x under-report, e.g. reporting $0.00123 as the doc's $0.123 example); this port uses the doc-verified /1e10 instead, confirmed against both the cost-tracking guide and the API reference's usage-object schema. Inspired-by: https://github.com/decolua/9router/pull/2453 Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> * docs: rename /implement-prs → /merge-prs in Hard Rule #21 (skill renamed 2026-07-11) (#6847) * docs: refresh stale llm.txt facts + relocate design.md to docs/architecture/DESIGN_SYSTEM.md (#6849) * docs: refresh stale llm.txt facts + move design.md to docs/architecture/DESIGN_SYSTEM.md llm.txt was frozen at the v3.8.8 era (177 providers, 37 MCP tools, 14 strategies, 9-factor scoring, 75% coverage gate). Update every factual claim to the current state (248 providers, 94 tools / 30 scopes, 18 strategies, 12-factor scoring, ratchet + 60% floor, TS 6, current docs/ layout) and re-sync the 42 exact-copy i18n mirrors. design.md at the root was a standardization plan whose phases 1-6 all shipped; rewrite its header as a permanent reference and relocate it to docs/architecture/DESIGN_SYSTEM.md per the root-hygiene policy (root = configs + canonical docs only). * docs: add MDX frontmatter to DESIGN_SYSTEM.md (in-app docs pipeline requires it) * feat: per-model web-search interception rule (#3384) (#6814) * feat(routing): per-model web-search interception rule (#3384) Adds a per-provider/per-model interceptSearch rule (src/lib/db/interceptionRules.ts, key_value namespace interception_rules) that overrides the existing native web-search bypass defaults (Codex/Gemini/Claude->Claude passthrough) in webSearchFallback.ts. Wired at the existing prepareWebSearchFallbackBody() call site in chatCore.ts. Resolution precedence: per-model rule > provider-level rule > existing native-bypass defaults. This lands Phase 1-2 of the plan (rule store + search interception). Web-fetch interception and the dashboard UI toggle are tracked as follow-up phases. * fix(db): register interceptionRules in localDb re-export layer (db-rules gate) * fix(db): renumber interception_rules migration 119→120 (collision with model_capability_overrides) * feat: sidebar search/filter input (#4013) (#6810) * feat(dashboard): add search/filter input to the dashboard sidebar (#4013) Adds a search box at the top of the expanded sidebar that filters nav sections/groups/items client-side by label, so users don't have to hunt through the growing nav tree. Reuses the existing common.search / common.noResults i18n keys (no new locale edits needed) and the shared Input icon="search" pattern. Matching sections auto-expand while searching and the accordion/pin state is restored once the query is cleared. Filtering logic is extracted into a pure filterSidebarSectionsByQuery() helper (src/shared/utils/sidebarSearch.ts) so it is trivially unit testable independent of React/next-intl/next-navigation. * fix(test): move Sidebar.search test to a runner-collected path (test-discovery gate) * fix(i18n): backfill 194 missing pt-BR keys (#6695) (#6723) * fix(i18n): backfill 194 missing pt-BR keys and add key-parity regression test (#6695) * Merge branch 'release/v3.8.47' into fix/6695-i18n-drift Resolve i18n key-parity and CHANGELOG-fragment conflicts: - Convert the #6695 CHANGELOG.md bullet to a changelog.d/ fragment (the fragment convention landed on release/v3.8.47 after this PR branched, per changelog.d/README.md). - Backfill 61 additional pt-BR keys that entered en.json on release/v3.8.47 after this PR's original 194-key backfill, so the PR's own key-parity regression test (tests/unit/i18n-pt-br.test.ts) stays green against the moving release baseline. * Discover live Codex models (#6776) * Add live model discovery for provider catalog * Fix model discovery request headers * fix(codex): sync live model limits with local catalog * test(codex): split live model discovery coverage into dedicated route tests * fix(codex): use chatgpt account id for live model sync * Add GitHub-backed Codex model discovery fallback * fix(providers): tighten oauth config tests and provider model display comments * test: align client version expectations with release default * fix(codex): keep discovery complexity within baseline * fix: rebase live Codex model discovery onto release/v3.8.47, preserving kimi-web buildHeaders (#6308) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(codex): echo requested effort-suffixed model id in Responses payloads (#3697) (#6820) * feat(codex): echo requested effort-suffixed model id in Responses payloads (#3697) Codex CLI compatibility shim: the Responses API response.created/ response.in_progress/response.completed payloads now carry a `model` field (previously absent), and for Codex-CLI-originated requests it echoes the client-requested effort-suffixed model id (e.g. gpt-5.5-xhigh) instead of the bare upstream id (gpt-5.5), so the Codex CLI status line/model button shows the active reasoning effort. - openai-responses.ts translator threads the upstream model into the Responses event objects (additive, omitted when unknown). - New isCodexOriginatedHeaders() (codexIdentity.ts) reuses PR #3481's originator/User-Agent detection, header-based so it still fires when a combo routes codex/gpt-5.5-xhigh to a non-codex upstream. - chatCore's existing opt-in #1311 echoModel pipeline now also fires automatically for Codex clients on the Responses API, regardless of the echoRequestedModelName setting. - responseModelEcho.ts now also rewrites the nested response.model field the Responses API uses (previously only top-level model). - /v1/models keeps returning models: [] for Codex (unchanged, #3481). Regression guard: tests/unit/codex-effort-model-echo-3697.test.ts. Closes #3697 * chore(merge): re-sync with release/v3.8.47 (restore CHANGELOG, keep own bullet) * chore(merge): re-sync with release/v3.8.47; move changelog bullet to changelog.d fragment (merge-storm proof) * feat(usage): surface Antigravity weekly quota alongside the 5-hour window (#4017) (#6818) * feat(usage): surface Antigravity weekly quota alongside the 5-hour window (#4017) Antigravity enforces both a 5-hour and a weekly usage limit, but the agy/antigravity quota widget only exposed the 5-hour window. The weekly limit isn't in the per-model retrieveUserQuota response already fetched — it lives in a separate, undocumented retrieveUserQuotaSummary RPC that groups models into families (Gemini Models, Claude and GPT models) with one weekly bucket per family. Adds a self-contained usage/antigravityWeeklyQuota.ts leaf: a cached, best-effort fetch of that RPC + a pure parser that extracts the weekly-labeled bucket per group (window inferred from bucketId/displayName text, matching the reverse-engineered shape documented by third-party Antigravity clients) into gemini_weekly/ claude_gpt_weekly quota entries, merged into the existing quotas map the widget already renders generically. A failed/unavailable RPC never affects the existing per-model quotas. Live VPS validation attempt (192.168.0.15, real antigravity account): both retrieveUserQuota and retrieveUserQuotaSummary currently return 429 RESOURCE_EXHAUSTED for that account, so the live response shape could not be captured directly. The parser was instead validated via TDD against the bucket shape documented by CodexBar (steipete/CodexBar), a third-party Antigravity client that reverse-engineered the same RPC, and is defensive against both response envelopes it has observed (top-level groups[] and nested quotaSummary.groups[]). * chore(merge): re-sync with release/v3.8.47 (restore CHANGELOG, keep own bullet) * chore(merge): re-sync with release/v3.8.47; move changelog bullet to changelog.d fragment (merge-storm proof) * feat: add Z.ai Web free web-cookie provider (#4056) (#6823) * feat(providers): add Z.ai Web free web-cookie provider (#4056) New zai-web web-session provider drives the free chat.z.ai consumer chat UI via a pasted browser cookie, distinct from the existing API-key zai/glm/glm-cn/glmt providers (api.z.ai). ZaiWebExecutor posts to chat.z.ai/api/chat/completions with the cookie forwarded both as Cookie and Authorization: Bearer <token>, and normalizes both z.ai's internal delta_content/phase SSE envelope and a pass-through OpenAI-shaped choices[].delta frame into standard chat-completion chunks. Registered in WEB_COOKIE_PROVIDERS, WEB_SESSION_CREDENTIAL_REQUIREMENTS, the provider registry (GLM-4.6/4.5/4.5V models), the executor factory, and tokenExtractionConfig.ts for in-app cookie capture. * fix(providers): regenerate translate-path golden for zai-web + reduce cognitive complexity * fix(providers): rename ZaiWebExecutor.buildHeaders to avoid incompatible BaseExecutor override * chore(merge): re-sync with release/v3.8.47; move changelog bullet to changelog.d fragment (merge-storm proof) * fix(codex): bump default client version to 0.144.0 (#6780) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(usage): extract per-group parsing in antigravityWeeklyQuota (cognitive-complexity gate 886→885, release-level drift from #6818 merge) * ci(quality): cut PR gate wall time without dropping protection (#6716) Collapse duplicate CI spend while keeping each gate's existence reason: - quality.yml: TIA __RUN_ALL__ defers full unit to fast-unit 4-shard (#6781); path filters via classify-pr-changes; docs-gates split; draft skip - ci.yml: wire docs/i18n/code path filters; ESLint JSON artifact for quality-gate; drop advisory typecheck:noimplicit; float actions/cache@v6 - TIA parity: memory/usage/combo/serial; **/*.test.mjs any depth; electron/bin no longer force unit __RUN_ALL__ - check:complexity-ratchets: one ESLint walk, ruleId-isolated baselines + cache - check:api-docs-refs + lib/apiRoutes: shared API route inventory - husky pre-push: intentionally light (gates live in pre-commit); CLAUDE.md + QUALITY_GATES.md docs synced - collect-metrics / lint:json: path.resolve cache path; Windows-safe eslint bin - env-doc allowlist for ESLINT_RESULTS_JSON / COMPLEXITY_ESLINT_REPORT - release-green --full-ci expects check:api-docs-refs (not docs-symbols alone) Tests: select-impacted, classify-pr-changes, api-routes lib, complexity-rule-count, validate-release-green. Reconciled after #6781 (fast-unit 2→4 shards) per maintainer request on #6716. Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> * fix(docs): document Turbopack build memory tradeoff for RAM-constrained machines (#6409) (#6885) * fix(routing): recognize Kimi token-limit 400 as context overflow for combo fallback (#6637) (#6893) combo.ts's isContextOverflow400() guard required the literal word 'context' in the 400 error body before letting a combo fall through to the next target. Kimi's exact wording ('Your request exceeded model token limit: 262144 (requested: 308458)') never says 'context', so the guard misclassified it as a body-specific error and halted the whole combo instead of trying the next (larger-context) target. accountFallback.ts's CONTEXT_OVERFLOW_PATTERNS already recognized this wording one layer below (via checkFallbackError -> shouldFallback), so the two independently-maintained classifiers disagreed and the stricter one won. Export CONTEXT_OVERFLOW_PATTERNS from accountFallback.ts and reuse it inside combo.ts's isContextOverflow400() so both layers share a single source of truth. Regression test: tests/unit/repro-6637-kimi-token-limit.test.ts (RED on unfixed code -> GREEN after the fix). Existing #4519 guard tests (tests/unit/combo-param-validation-fallback-4519.test.ts) still pass, including the negative case that a genuinely body-specific 400 is NOT misclassified as overflow. * fix(providers): honor a provider-level proxy assigned to no-auth providers (#6272) (#6895) No-auth providers (mimocode, opencode, ...) are always dispatched with a single hardcoded connectionId ("noauth" — SYNTHETIC_NOAUTH_CONNECTION_ID in src/sse/services/auth.ts). No provider_connections row ever has id="noauth", so resolveProxyForConnection() in src/lib/db/settings.ts could never populate connectionRecord for them, and its provider-level proxy lookup (Steps 6/8) only runs when connectionRecord is present. A proxy assigned via Settings -> Providers -> mimocode was therefore silently ignored, reproducing the reporter's "same thing happen when i set the proxy directly in the provider menu" symptom. Adds a best-effort fallback (src/lib/db/settings/noAuthProxyFallback.ts): when connectionRecord could not be resolved, scan the known no-auth provider ids for a configured provider-level proxy (registry first, then legacy) before falling through to the global/direct steps. Regression test: tests/unit/proxy-noauth-provider-6272.test.ts (RED on unfixed code — resolved to level=direct/proxy=null; GREEN after the fix). * fix(dashboard): surface Claude extraUsage credits in quota card (#6806) (#6896) Enterprise-tier Claude accounts (default_raven_enterprise) don't get five_hour/seven_day utilization windows from Anthropic's OAuth usage endpoint — only an extra_usage credit-billing block. parseClaude() only read data.quotas, so quotas stayed {} and the dashboard showed "No quota data" even when extraUsage showed the account 100% exhausted. parseClaude() now folds an enabled extraUsage block into a credits-style quota row (mirroring parseCodex's bankedResetCredits pattern), both when quotas is empty and when it's already populated. * fix(db): share sql.js preinit across callers, fix named-param bind (#6628, #6802) (#6899) - preInitSqlJs() now memoizes an in-flight Promise (not just the resolved adapter) per filePath, so concurrent BATCH/STARTUP/HealthCheck/ ProviderLimitsSync callers at boot share one full-file read+WASM decode instead of each independently reloading the whole database — the thundering-herd amplifier of the OOM condition #6632 already partly fixed, left un-implemented by the reporter's own proposed fix (#6628). - sqljsAdapter's run/get/all now unwrap a lone named-parameter object (e.g. .all({ isActive: 1 }) for "WHERE is_active = @isActive", the same call shape getProviderConnections() already uses against better-sqlite3) before calling sql.js's stmt.bind(), expanding it to the @/:/$ sigil variants sql.js's own named-bind path requires. Previously the object was wrapped into an array and sql.js took the positional-bind path, throwing "Wrong API use : tried to bind a value of an unknown type ([object Object])." whenever the sql.js WASM fallback driver was active — exactly the error #6802 reported (misattributed to better-sqlite3). Regression tests added to tests/unit/db-adapters/driverFactory.test.ts and tests/unit/db-adapters/sqljsAdapter.test.ts, both proven RED against the prior code and GREEN after the fix. * fix(plugin): split OC-gate provider id from OmniRoute-facing routing id (#6859) (#6900) resolveOmniRoutePluginOptions() auto-prefixes providerId with "opencode-" (commit75b52e286) so OpenCode 1.17.8+'s native-adapter gate accepts it as a registered provider id. That prefixed value was being reused for the OmniRoute-server-facing identifiers too: mapRawModelToModelV2's id/providerID, mapComboToModelV2's providerID, and the dynamic provider hook's combo catalog keys. OmniRoute's server has no "opencode-<x>" provider alias, so every dispatched model failed credential lookup with "No credentials for opencode-omniroute" / "No active credentials for provider: opencode-omniroute". Add a … * test(ci): static body in codex e2e mock route bridge (CodeQL #737) (#7559) CodeQL js/stack-trace-exposure flags ANY error-derived value returned in the mock route bridge's 500 path, not just error.stack — swapping .stack for error.message (in #7354, alert #736) left sibling alert #737 open on the same line. Replace the body with a static string; the test only asserts status===200, so the 500 body is never inspected. Clears the last open CodeQL alert repo-wide, unblocking the Quality Ratchet on every PR. Companion to the release/v3.8.49 PR (merge-gates §8 — gate/CI-touching fix lands on main in the same session). * fix(security): bump adm-zip >=0.6.0 + exact host matching in mitm DNS test (#7733) * chore(deps): resolve 7 open Dependabot alerts via npm overrides (#8067) - fast-uri ^3.1.3 (root + electron) — host confusion via IDN (#131/#126, high) - hono ^4.12.27 — JSX ctx isolation / cx() XSS / v1 adapter req drop (#128/#129/#130, medium) - @hono/node-server ^2.0.5 — serve-static path traversal (#127, medium); MCP uses only getRequestListener, not serve-static - body-parser ^2.3.0 — DoS on invalid limit (#125, low) Resolved: fast-uri 3.1.4, hono 4.12.31, @hono/node-server 2.0.11, body-parser 2.3.0. All clear in npm audit; lockfile-lint OK. * chore(deps): resolve 3 Dependabot alerts (dompurify, fast-xml-parser, sharp) (#8070) - dompurify ^3.4.12 (#132, low), fast-xml-parser ^5.10.1 (#133, high DOCTYPE), sharp ^0.35.0 (#134, high libvips) Resolved: dompurify 3.4.12, fast-xml-parser 5.10.1, sharp 0.35.3. All clear in npm audit; lockfile-lint OK. * docs(readme): use local SVG flags + convert doc-link tables to lists (#8317) Flag emoji (regional-indicator) do not render on Windows and several Safari/WebKit configs, breaking the 'In 42+ languages' table there. Replace each emoji with a local SVG <img> from flag-icons (MIT), served from docs/assets/flags/ so the language selector renders everywhere. Also convert the 5 'Document | Description' reference tables to definition lists — full-width and scroll-free on GitHub (tables are capped at width:max-content and scroll horizontally on narrow viewports). * fix(i18n): restore brand proper nouns and unify terminology in zh-CN and zh-TW - Restore untranslated brand/model proper nouns (Claude, OpenAI, Anthropic, Gemini, MiniMax, etc.) in zh-CN and zh-TW - Replace Chinese phonetic/translation forms (克劳德->Claude, 打开Ai->OpenAI, 人择->Anthropic, 双子座->Gemini, etc.) - Unify 'provider' translation to '供应商'/'供應商' (was inconsistent: 提供者/服务商) - Apply zh-TW localized terminology (網路/設定/檔案/新增/啟用/搜尋/儲存) - Fix 'providers' value bug in zh-CN * docs: sync env-var contract (chaos panel, notion TLS, grok auth path) + repair glued VNC line in .env.example (#8362) * Add comparison and zero-config installation diagrams in SVG format - Created a comparison table SVG illustrating the capabilities of OmniRoute versus competitors (9router, OpenRouter, CLIProxyAPI, LiteLLM) across 13 features. - Added a zero-config installation SVG demonstrating the ease of setting up OmniRoute with three simple steps: installation, pointing to the tool, and receiving instant replies. * fix(runtime): isolate unique 8177 repairs (#8298) * perf(api): singleflight version lookups (#8278) (#8301) Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> * fix(providers): adapt Kimi nonstream requests internally (#8302) Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> * fix(mcp): keep POST SSE responses uncompressed (#8303) Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> * fix(cpa): isolate credential-pool failures (#8308) * fix(cpa): isolate credential pool failures Co-Authored-By: Claude <noreply@anthropic.com> * fix(cpa): forward transport through the chatCore key-health wrapper The local recordKeyHealthStatus wrapper in handleChatCore only declared (status, creds), so the transport argument added for CPA credential-pool isolation was silently dropped at the call site (TS2554 "Expected 2 arguments, but got 3" once chatCore.ts is typechecked with tsc directly — this file is not in tsconfig.typecheck-core.json's file list, so `npm run typecheck:core` did not surface it). The CPA isolation guard in keyHealth.ts never received `transport`, so it never fired. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(sse): suppress </think> by default on Chat Completions (#8245) (#8309) Claude→OpenAI translation was emitting a literal </think> into delta.content for ordinary Chat Completions clients. Reasoning already ships as reasoning_content, so default to suppress and keep x-omniroute-thinking-marker: on as the #4633 opt-in. * fix(sse): preserve Responses combo payloads (#8310) * fix(providers): classify HTTP 400 model-unavailable as MODEL_NOT_FOUND so Antigravity Pro fallback locks out the deprecated model (#8319) * fix(providers): carve cookie-auth providers out of terminal 401 'expired' classification so one 401 cooldowns instead of killing the connection (#8321) * fix(api): fold namespace into the flattened Chat tool name so cross-namespace leaves do not collide (#8322) * fix(providers): classify per-model-quota 403 and DEGRADED 400 as model-unhealthy in checkFallbackError (#8247, #8248) (#8323) * fix(providers): route noauth opencode-zen connections through their assigned proxy (#8324) * fix(sse): re-export PROVIDER_BREAKER_FAILURE_STATUSES for the orphaned all-rate-limited breaker path (#8390) Root cause: #8013 extracted shouldTripProviderBreakerForResult() from src/sse/handlers/chat.ts into the new src/sse/handlers/chatPredicates.ts, taking the (non-exported) const PROVIDER_BREAKER_FAILURE_STATUSES with it. A second, independent use of that const survived in chat.ts's handleSingleModelChat(), in the "all credentials rate-limited" block (~line 1340) — that reference was left orphaned by the extraction. Production impact: any request where every credential for a provider+model is simultaneously rate-limited throws `ReferenceError: PROVIDER_BREAKER_FAILURE_STATUSES is not defined` at runtime in that code path. Concretely this meant: - breaker._onFailure() was unreachable on the all-rate-limited path, so the provider circuit breaker could not trip from it - the ReferenceError propagated up and got mapped to a generic 502, masking the real 503 upstream-unavailable status in combo responses - the issue-agent route surfaced a generic 400 instead of the actual 429 provider-rate-limited response Fix: export PROVIDER_BREAKER_FAILURE_STATUSES from chatPredicates.ts and add it to chat.ts's existing import block from that module. No behavior change — the classification set ([408, 500, 502, 503, 504]) is unchanged, this only repairs the broken reference. Also re-points tests/unit/nvidia-quota-phase1.test.ts's regex-based declaration check at chatPredicates.ts, where the const now actually lives (it previously read chat.ts via fs+regex and silently failed to find the declaration). The regex and the classification assertions themselves are unchanged — this test still proves 429 is excluded from the whole-provider breaker. Refs #8013 * fix(sse): gate reasoning-placeholder strip to chunks that contain the sentinel (#8382) Regression: #8162 (port of #8081) added an unconditional `.trim()` to stripInternalReasoningPlaceholder(), applied to every streaming delta.content chunk across 3 call-sites (openai-to-claude.ts, openai-responses.ts, responsesTransformer.ts). Leading/trailing whitespace at a chunk boundary is a real word boundary between streaming fragments; trimming it glues adjacent chunks together on the client ("Hello, " + "world." + " Bye." -> "Hello,world.Bye."). Fix: early-return via .includes() before the replaceAll+trim, so the function is a true no-op when the sentinel is absent from the chunk. Behavior when the sentinel IS present is unchanged. Validation: - tests/unit/streaming-reasoning-dedup-5786.test.ts: the "(A-guard)" test was RED on the base branch ('Hello,world.Bye.' vs 'Hello, world. Bye.'); GREEN after the fix (4/4 passing). - tests/unit/translator-resp-openai-to-claude.test.ts: added a new multi-chunk boundary-whitespace regression test, proven RED against the pre-fix code (12/13), GREEN after (13/13). - No regressions in responses-transformer.test.ts (17/17), responses-transformer-dense-output.test.ts (3/3), or the other suites exercising the shared placeholder utility (160/160 total across all consumers). Refs #8162 Refs #8081 * fix(resilience): terminal-skip spares the recoverable GitHub Copilot no_refresh_token state (#8389) Cause: #8182's terminal-connection guard in checkConnection() returns early for any testStatus in {credits_exhausted, banned, expired} to stop the sweep from wasting CPU/network probing connections that can never self-heal. But testStatus="expired" + errorCode="no_refresh_token" is exactly the state the pre-existing GitHub Copilot self-heal targets (isGitHubAccessTokenOnlyConnection + canClearGitHubNoRefreshTokenState, ~line 83-97 / 413-492): a Copilot connection with no OAuth refresh token but a still-valid copilotToken, which the sweep is supposed to flip back to "active". With the new guard placed ahead of that block unconditionally, the self-heal became unreachable for exactly the state it exists to clear. Impact: healthy GitHub Copilot connections that once lost their OAuth refresh token got stuck at testStatus="expired" in the dashboard forever, even though their Copilot sub-token kept working and the sweep would have cleared the stale status back to "active" every cycle before #8182. Fix: carve out the exact recoverable shape from the terminal-skip guard — testStatus==="expired" && errorCode==="no_refresh_token" && isGitHubAccessTokenOnlyConnection(conn) — so the guard still skips every other terminal case (credits_exhausted, banned, and "expired" for any other reason) untouched, matching #8182's original intent. Validation: tests/unit/token-health-no-refresh-token-expired-5326.test.ts was red (1 fail / 4 pass) before the fix — "checkConnection clears stale no_refresh_token state for usable GitHub Copilot connections" asserted testStatus flips back to "active" but got "expired". Green after the fix (6/6, including a new boundary test proving a GitHub Copilot connection expired for any OTHER reason, e.g. errorCode "invalid_grant", is still skipped untouched). Also reran the adjacent checkConnection/tokenHealthCheck suites (token-health-check.test.ts, token-health-check-circuit-breaker.test.ts, apikey-connection-health-check.test.ts, token-health-check-sweep.test.ts, token-health-check-tickms-defined.test.ts, tokenHealthCheck-batchSize.test.ts, codex-oauth-refresh-persist-6352.test.ts, oauth-providers-error-handling.test.ts) — all green, confirming #8182's terminal-skip behavior is otherwise unchanged. npm run typecheck:core clean. Refs #8182 Refs #8286 Refs #5326 * fix(sse): cap exact cooldowns only when synthetic — verified upstream resets pass uncapped (#8393) Contract vs cap: #6863 requires a model lockout to honor a VERIFIED upstream quota reset exactly (e.g. Antigravity "Resets in 92h27m28s", shipped in v3.8.47). #7940 requires SYNTHETIC exact-cooldown estimates (the quota_exhausted until-midnight heuristic) to respect the operator's maxCooldownMs so they cannot balloon unbounded. Both are legitimate, non-conflicting contracts — they apply to different kinds of values. Root cause: #7980 (fixing #7940) changed recordModelLockoutFailure() in open-sse/services/accountFallback.ts to unconditionally clamp every exactCooldownMs against maxCooldownMs, with no way to distinguish a verified upstream reset from a synthetic estimate. A real ~92h reset got clamped to the operator's ~30min cap, and the router went on hammering 429 against quota that was known not to recover for days — regressing #6863's contract by omission, not by new policy (the "honor it exactly" docstrings on selectLockoutCooldownMs() and its call sites were left untouched and now describe dead code). Fix: add an opt-in `exactCooldownVerified` flag to recordModelLockoutFailure()'s options. When true, exactCooldownMs bypasses the maxCooldownMs clamp entirely; when false/omitted (the default), behavior is byte-identical to before this change. Set the flag only at the 4 call sites that already carry upstream provenance for the value they pass — usedUpstreamRetryHint / quotaResetHintMs from checkFallbackError(): - open-sse/services/combo.ts (2 sites): exactCooldownVerified mirrors lockoutHintMs > 0, which is only ever nonzero when it traces back to a genuine upstream signal. - src/sse/services/auth.ts (2 sites): exactCooldownVerified mirrors the same usedUpstreamRetryHint / quotaResetHintMs check already used to derive exactCooldownMs at each site. The quota_exhausted → until-midnight synthetic default and plain exponential backoff are untouched and stay capped, per #7940. The two other recordModelLockoutFailure call sites (combo.ts quality failure, auth.ts local-404/grok-web-403) never carry a verified hint and were left unmodified. Validation (TDD): tests/unit/combo-lockout-quota-reset-6863.test.ts red→green with its assertions unchanged (was clamping ~332,848,000ms to ~1,799,995ms; now honors the parsed reset). Added a boundary pair to tests/unit/model-lockout-exact-cooldown-cap.test.ts proving the same magnitude resolves differently by provenance: synthetic stays capped, verified passes through whole. Full existing suite in that file plus combo-model-lockout-honors-reset-1308.test.ts stay green unmodified. Swept 45 lockout/cooldown-adjacent test files (502/505 passing); the 3 failures reproduce byte-identical on a pristine origin/release/v3.8.49 checkout (PROVIDER_BREAKER_FAILURE_STATUSES ReferenceError in untouched chat.ts, and a documented timing-sensitive serial test) — confirmed pre-existing, out of this fix's scope. npm run typecheck:core and npm run lint are clean. Refs #6863 Refs #7940 Refs #7980 * fix(sse): family auto-combos include any backend that serves the family (no-auth allowlist scoped to tier pools) (#8391) Context: #8183 introduced AUTO_COMBO_NOAUTH_ALLOWLIST (opencode, felo-web) to gate no-auth providers out of every auto/* candidate pool, motivated by public HTTP egress reliability on the reference VPS (.15) — several no-auth backends (duckduckgo-web, theoldllm, chipotle, aihorde) were flaky there. Its own tests (noauth-autocombo-allowlist.test.ts, virtual-auto-combo.test.ts) never exercised the auto/<family> path, so the gate silently applied there too. auto/<family> combos (#6453, e.g. auto/glm, auto/zai) are a different axis: an identity selector ("route to whatever genuinely serves GLM"), not a reliability-curated pool. auggie (local CLI subprocess, zero HTTP egress — the reliability concern #8183 targets doesn't even apply to it) advertises a literal glm-5.2 model and had an explicit design-test seat in auto/glm since #7032, but the #8183 allowlist silently excluded it from that pool. Operator decision (2026-07-24): the no-auth allowlist gate keeps applying to category/tier and flat-variant auto/* pools (auto/best-free, auto/coding:fast, ...), but auto/<family> pools bypass it — any no-auth backend that genuinely serves the family is admitted. Fix: thread a `bypassAllowlist` flag through isChatAutoComboNoAuthProvider() and getNoAuthCandidates(), set to `Boolean(spec?.family)` at the single call site in createVirtualAutoCombo(). Family narrowing (buildFamilyCandidateFilter) still runs afterward, so a bypassed no-auth candidate only survives if its model actually belongs to the requested family. Category/tier and flat-variant pools (spec.family unset) keep the gate fully intact. Validation: - tests/unit/autoCombo/provider-family-combos.test.ts:136 was red (expected ["auggie","glm","zai"], got ["glm","zai"]) — now green (11/11 passing). - tests/unit/noauth-autocombo-allowlist.test.ts (3/3) and tests/unit/virtual-auto-combo.test.ts (10/10, including "restricts the no-auth pool to the allowlist") stay green — the #8183 gate is untouched for spec-less/category/tier pools. - Full tests/unit/autoCombo/ vitest sweep: 5 files, 36/36 passing. - npm run typecheck:core clean. - 4 unrelated failures pre-exist on origin/release/v3.8.49 (verified via `git show HEAD:<path>` swap, no stash) in tests/unit/auto-combo-credentialed-model-pool.test.ts (antigravity/gemini-3.5 credentialed-pool logic, untouched by this change). Refs #8183, Refs #6453, Refs #7032 * fix(compression): rank codex-responses in adaptive-ladder maps (#8381) #8010 registered the codex-responses engine in the compression catalog (engineCatalog.ts, stackPriority 12 between rtk's 10 and headroom's 15) but never added it to adaptiveCompression/ladder.ts's AGGRESSIVENESS and REDUCTION_FACTOR maps. Those maps' own header documents that they must cover every real catalog/registry engine, not just the 7 in DEFAULT_LADDER, so an operator adding codex-responses via ladderOverride silently fell back to aggressivenessOf() === 0 (same as "off") and expectedReductionFactor() === 0.9 (the generic default), breaking floor-mode escalation ranking for any ladder that includes it. Add "codex-responses" to both maps between rtk and ionizer, matching its stackPriority (12) sitting between rtk's (10) and ionizer's (13): - AGGRESSIVENESS: 22 (between rtk's 20 and ionizer's 25) - REDUCTION_FACTOR: 0.84 (between rtk's 0.85 and ionizer's 0.83), reflecting its "lossless-first, bounded diagnostic" guidance in engineCatalog.ts Validation: tests/unit/ladder-engine-maps-6533.test.ts red -> green (2 of 3 tests were failing on the missing engine; all 3 pass after the fix). Sanity-checked neighbors compression-exclusions.test.ts and compression/adaptive-resolve-plan.test.ts still pass. Refs #8010 * fix(i18n): restore #8219 CacheSettingsTab key sync + synthetic fixture for zh-TW repro test (#8387) Root cause (two independent causes): 1. PR #8219 (commit2a865aaaa7) added CacheSettingsTab.tsx with 12 t("settings.*") calls whose keys were never created anywhere, not even in en.json (the source of truth). The same PR added only 3 sidebar/header keys (settingsCache, settingsCacheSubtitle, settingsCacheDescription) to en+es, without running `npm run i18n:sync-ui` to propagate to the other 41 locales. 2. tests/unit/i18n-missing-placeholder-fallback.test.ts had a "#7258 repro" test asserting the real zh-TW.json still carried raw __MISSING__: placeholders — a premise invalidated by #8024, which completed the Traditional Chinese translation to 100%. What changed: - Added the 12 missing settings.* keys to en.json, mirroring the sibling requestBodyLimit* family (placeholders {min}/{max}/{value} match the component exactly). - Added real, natural translations for all 15 CacheSettingsTab-related keys (12 settings.* + 3 sidebar/header) to pt-BR.json, vi.json and es.json (es already had the 3 sidebar/header keys). - Ran `npm run i18n:sync-ui` (official tool, no locale hand-edited) to stub the remaining 39 locales with __MISSING__:<english>. This also discovered 17 pre-existing unrelated missing keys (compression-exclusions settings, 8 new-provider onboarding descriptions) never synced since #8031, and pruned 3 dead orphaned zh-TW-only keys (codexSessionAffinity{Title,Desc, Ttl}, superseded by the generic sessionAffinity* keys since #7274, confirmed unused anywhere in src/) — verified programmatically as +32/-0/~0 changed per stub locale, +32/-3/~0 changed for zh-TW. - Rewrote the "#7258 repro" test to use a synthetic fixture (same style as the sibling deepMergeFallback fixtures in the same file) instead of depending on zh-TW.json's real, evolving translation-completeness state. Proves the same behavior: collectPlaceholderLeaves() detects a raw __MISSING__: leaf before deepMergeFallback (the fix) is exercised. Validation: all 4 previously-red files green (23/23 assertions). Broader sweep of 271 i18n-adjacent unit tests unaffected. i18n:check-ui-coverage (42/42 locales >=80%, 99.7-100%) and i18n:check-glossary both pass. typecheck:core clean. Refs #8219 Refs #8024 * chore(quality): drop stale muse-spark-web allowlist entry + sync sidebar order snapshots (#8383) Two independent "code is right, bookkeeping lagged" base-reds: 1. #8233 made open-sse/executors/muse-spark-web.ts import sanitizeErrorMessage from utils/error.ts (a real Rule #12 fix), but left its KNOWN_MISSING_ERROR_HELPER allowlist entry in scripts/check/check-error-helper.mjs in place. The gate's own stale-allowlist enforcement (assertNoStale) correctly flagged the now -obsolete entry: `npm run check:error-helper` failed with "1 entrada(s) obsoleta(s)", and tests/unit/check-error-helper.test.ts's "the shipped allowlist freezes exactly the known current violators" test expected an empty Set. Removed the entry (kept the assertNoStale machinery and the general scope-header comments untouched). 2. #8064 added the "compression-exclusions" sidebar item right after "compression-studio" in COMPRESSION_CONTEXT_GROUP (deliberate, complete feature) but didn't update two order-snapshot tests written before that item existed: - tests/unit/sidebar-visibility.test.ts expected the "omni-proxy" section's flattened id list to end the compression block at "compression-studio". - tests/unit/ui/sidebar-engine-items.test.ts asserted "Studio must be last" in COMPRESSION_CONTEXT_GROUP. Updated both to the real, intentional order: Settings -> Combos -> engines -> Studio -> Exclusions (Studio now second-to-last, Exclusions last). Validation (red -> green): - check:error-helper gate: red ("1 entrada(s) obsoleta(s)") -> green ("OK (898 files scanned, 0 known-missing frozen)") - tests/unit/check-error-helper.test.ts: 31/32 -> 32/32 - tests/unit/sidebar-visibility.test.ts: 6/7 -> 7/7 - tests/unit/ui/sidebar-engine-items.test.ts: 13/14 -> 14/14 Refs #8233 Refs #8064 * test: realign catalog snapshot tests to current deliberate catalog state (#8386) * test: realign catalog snapshot tests to current deliberate catalog state Six catalog/snapshot tests drifted behind deliberate catalog changes that were already validated by newer sibling tests. No production code touched; every change aligns a stale snapshot to behavior already validated by newer sibling tests. Root causes (all confirmed against the current code before editing): - tests/unit/providers-constants-split.test.ts: APIKEY_PROVIDERS grew from 187 to 195 entries via #8077 (clova-studio/internlm/ant-ling, regional), #8161 (sarvam/plamo → regional, writer → frontier-labs) and #8170 (typhoon → regional, inception → frontier-labs). Family counts verified to sum to 195 (gateways 60, frontier-labs 24, inference-hosts 28, enterprise-cloud 17, regional 40, specialty-media 26) with no duplicates. Updated the two assertions and extended the changelog comment. - tests/unit/qianfan-provider.test.ts: the expected Baidu Qianfan website URL was the pre-#8128 wenxinworkshop path. #8128/#6271 moved it to https://cloud.baidu.com/product-s/qianfan_home, already locked by the sibling regression test tests/unit/baidu-qianfan-website-urls-6271.test.ts. - tests/unit/t31-t33-t34-t38-model-specs.test.ts and tests/unit/auto-combo-credentialed-model-pool.test.ts: the Antigravity catalog refactor (#8013) retired gemini-3-pro-preview/claude-sonnet-5 and renamed the Gemini 3.5 Flash tiers (low/medium/high -> extra-low/low/gemini-3-flash-agent), confirmed against ANTIGRAVITY_PUBLIC_MODELS and tests/unit/antigravity-retired-public-models.test.ts. Swapped the retired IDs for currently-registered ones (gemini-3.6-flash-high, claude-sonnet-4-6, gemini-3-flash-agent, gemini-3.5-flash-low/extra-low) and moved the wildcard-exclusion prefix test from the now-2-tier "gemini-3.5-*" group to "gemini-3.6-*", which has 3 real tiers today (same >=3 semantics, just pointed at a prefix that still has 3 members). - tests/unit/model-alias-seed.test.ts: getModelInfo("gemini-3.1-pro") now canonicalizes through ALIAS_TO_PROVIDER_ID["agy"] = "antigravity" (#8050), the same pattern already applied to opencode -> opencode-zen. Updated the expected provider id. - tests/unit/video-dashscope.test.ts (deleted, 216 lines): #8266 reorganized the Alibaba video catalog so the flat wan2.7-t2v id no longer exists under the plain "alibaba" provider (only the dated wan2.7-t2v-2026-06-12 does); the flat id now lives only under "qwen-cloud". All 6 tests in the file failed because they built requests against alibaba/wan2.7-t2v, which the new allowlist now rejects with 400 ("unsupported alibaba video model") - verified directly against VIDEO_PROVIDERS in open-sse/config/videoRegistry.ts. Coverage already exists and was confirmed passing pre-deletion in tests/unit/alibaba-video-media.test.ts (including an explicit "Alibaba rejects video models outside its own allowlist" case for this exact id) and tests/unit/qwen-cloud-video-media.test.ts (covers the same id under qwen-cloud). Note: the deleted file's DashScope upstream error-path assertions (401 missing credentials, 502 missing task_id, 502 FAILED status, 504 poll timeout) don't have a byte-for-byte equivalent in the two replacement files, though the shared dashscopeHandler.ts code path they exercise remains covered by several sibling *-media.test.ts files for the happy path and local validation. - tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts: #7892 added /api/vnc-session to the SPAWN_CAPABLE_PREFIXES deny-list (Hard Rules #15/#17 hardening). Bumped the expected length 10 -> 11 and added the entry to the test's named list for documentation. Refs #8013, #8050, #8266, #7892, #8128 * chore(quality): allowlist the video-dashscope.test.ts deletion with its replacements check:test-masking (pr-test-policy CI gate) requires a _deletedWithReplacement entry for any deleted test file, even when the deletion is a verified-legitimate supersession. Documents the same #8266 rationale from the prior commit in the machine-checked allowlist so the deletion is not flagged as unexplained masking. Refs #8266 * test: hermetic notion thread-session mocks + drop duplicated usage-analytics suite (#8392) * test: hermetic TLS mock for notion thread-session suite + drop duplicated usage-analytics file Root cause A (#8159): sendNotionInferenceRequest() in open-sse/executors/notion-web.ts was migrated from fetch() to tlsFetchNotion() (open-sse/services/notionTlsClient.ts, native tls-client-node binary) to get past Notion's Cloudflare TLS fingerprinting. #8159 updated the mock in the sibling tests/unit/executor-notion-web.test.ts (installNotionTlsMock, wired through __setTlsFetchOverrideForTesting) but never touched tests/unit/executor-notion-web-thread-sessions.test.ts (split out earlier by #7900) — its 3 execute()-driven tests still mocked globalThis.fetch, which tlsFetchNotion() never calls once the native TLS client loads successfully. Confirmed live: all 3 tests hit real https://app.notion.com with a fake cookie and got a real 401 (~8.1-8.5s each here; on a network with blocked/slow egress this would instead hang up to the client's ~190s timeout+grace per test — a CI-hang risk). Fix: replicate installNotionTlsMock verbatim from the sibling file into executor-notion-web-thread-sessions.test.ts so the 3 tests mock the TLS override point instead of global fetch. Suite is now fully hermetic — 8/8 pass, no network I/O, total runtime 31.0s -> 8.0s. Root cause B (#7700): tests/unit/usage-analytics-route-extra.test.ts was created as a byte-for-byte duplicate of 10 of the 22 tests in tests/unit/usage-analytics-route.test.ts. #7300 later fixed a fixture bug in the retention-window boundary test ("does not double-count raw and aggregated rows") in the main file only — reading getUserDatabaseSettings().retention.usageHistory live instead of a hardcoded 30-day cutoff (default retention is 365 days) — leaving the duplicate copy on the stale hardcoded value, which now fails (1 !== 2). Fix: delete the duplicate file. All 10 of its test names exist verbatim in the main file (verified with comm -12) and that file passes 22/22: - does not double-count raw and aggregated rows - does not persist guessed API key attribution - does not throw Unknown named parameter on short range (needsAggregated=false) - does not throw Unknown named parameter with apiKey filter on long range - groups renamed API key usage by stable ID - includes activityMap for heatmap - includes cost by API key - omits global aggregates when filtering by API key - returns 500 on database errors - returns weeklyPattern for the costs dashboard No coverage loss — same production code, same assertions, one fewer redundant file. Validation: - RED executor-notion-web-thread-sessions.test.ts: 5 pass / 3 fail (401 !== 200, real network hit), 31.0s - RED usage-analytics-route-extra.test.ts: 9 pass / 1 fail (1 !== 2), 18.7s - GREEN executor-notion-web-thread-sessions.test.ts: 8/8 pass, 8.0s, hermetic (no network) - GREEN executor-notion-web.test.ts (sibling, untouched): 37/37 pass, byte-identical diff - GREEN usage-analytics-route.test.ts (untouched): 22/22 pass, byte-identical diff - npx eslint on the changed file: clean - npm run typecheck:core: clean (exit 0) Refs #8159 Refs #7300 Refs #7700 * chore(quality): register usage-analytics-route-extra deletion in test-masking allowlist check:test-masking hard-flags any deleted test file without a _deletedWithReplacement entry. The deletion is legitimate (100% duplicate suite, coverage retained verbatim in tests/unit/usage-analytics-route.test.ts) -- same registration pattern as the video-dashscope entry. Refs #7700 Refs #7300 * chore(ci): cancel superseded runs, skip DAST on docs-only PRs, persist TIA shadow evidence (#8379) Runner-cost pass grounded in the #8084 review of the current pipeline: - dast-smoke.yml: add concurrency cancel-in-progress (25-min advisory builds were stacking on force-push storms) and paths-ignore for docs/**+**/*.md — a docs-only PR cannot change DAST behavior but was paying the 6-11min CLI-bundle build. - semgrep.yml: add concurrency cancel-in-progress. No paths filter on purpose: p/secrets must keep scanning docs-only diffs (credentials leak in .md too). - quality.yml (TIA step): persist the per-PR impacted-test selection to a tia-selection artifact + GITHUB_STEP_SUMMARY line. This is the shadow-evidence phase: TIA false negatives become measurable against fast-unit's full-suite verdict across releases BEFORE any gate authority moves off ordinary PRs. Refs #8084 * docs: one golden path across PR template, CONTRIBUTING, GEMINI, AGENTS + CI milestones in ROADMAP (#8380) Contributor guidance contradicted itself in four places (found in the #8084 review): - pull_request_template.md + CONTRIBUTING.md asked contributors to run the FULL unit suite + coverage gate locally, while the maintainer's stated golden path (#8273/#8329) is: focused tests for the change locally; full suite, coverage, and build are CI's job. On 16GB hosts the full local chain has saturated machines (#8084 incident report). - GEMINI.md demanded coverage >= 75/75/75/70 while the official CI gate is 60/60/60/60 (quality-baseline ratchet on top). - AGENTS.md fork workflow said to branch from upstream/main; the default branch is the active release/vX.Y.Z line (main only receives release squash-merges). Also makes the #8084 CI direction explicit in the public ROADMAP: lane consolidation (3.8.51), one CI policy for release/** and main (3.8.52), full-regression authority -> merge queue after TIA shadow evidence (3.8.54), preview-artifact + build-once rehearsal inside the 3.8.58 dry-run. Refs #8329 Refs #8084 * fix(translator): cap thinking budget on explicit budget_tokens path (#8312) * fix(translator): cap thinking budget on explicit budget_tokens path * fix(translator): stop dropping thinkingConfig on cap-0 reasoning_effort path The thinking-budget-cap guard added in this branch skipped thinkingConfig entirely whenever a model's thinkingBudgetCap was 0 (e.g. gemini-3-flash), including on the reasoning_effort/budgetMap path. That regressed the pre-#6943 native-defaults contract (thinkingBudget 0 / includeThoughts false must still be present) and crashed callers that read `.thinkingConfig.thinkingBudget` unconditionally (translator-openai-to-gemini-defaults.test.ts). Also restore includeThoughts:true on the Claude-format explicit thinking.budget_tokens path (openai-to-gemini.ts's Claude-format field and claude-to-gemini.ts's native thinking field): budget_tokens:0 there is the client's dynamic-thinking sentinel (#6813), not an off-switch, and must stay true even after the new capping — the cap must only clamp positive explicit values, never flip the zero sentinel's semantics. Updates two tests this branch added that encoded the incorrect "omit thinkingConfig / includeThoughts:false for the 0 sentinel" behavior, to match the pre-existing, still-required contracts above. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): revert scope-creep flip of Gemini 3.5/3.6 Flash supportsThinking The thinking-budget-cap fix accidentally expanded 5 shorthand modelSpecs entries (gemini-3.5-flash, gemini-3.5-flash-low, gemini-3.6-flash-high/ medium/low) into explicit objects setting supportsThinking:true and thinkingBudgetCap:24576. That flip was unrelated to the two proven test regressions (translator-openai-to-gemini-defaults.test.ts and claude-to-gemini-budget-tokens-zero-6813.test.ts, which only exercise gemini-3-flash-preview, gemini-3.1-pro and gemini-2.5-pro) and reopens a deliberately closed path from #8013: Antigravity still rejects client-supplied thinking params for these Gemini 3.5/3.6 Flash tier ids, so supportsThinking must stay false (inherited from GEMINI_35_FLASH_MODEL_SPEC). Reverted all 5 entries back to the release shorthand `{ ...GEMINI_35_FLASH_MODEL_SPEC }`. gemini-3-flash, gemini-3.1-pro and gemini-2.5-pro (the models the regression tests actually exercise) were already correctly specced in the release baseline and are untouched. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(providers): reconcile Kimi K3 vision when attachment contradicts modalities (#8250) (#8313) * fix(providers): reconcile Kimi K3 vision when attachment contradicts modalities Synced models.dev rows for kimi-coding*/k3 can ship attachment=false while modalities_input still lists image/video. Prefer the modality signal (and normalize at sync + resolve) so supportsVision, attachment, and exposed modalities agree. Closes #8250 * fix(providers): keep Kimi K3 static fallback text-only (#8250) The Kimi K3 vision reconciliation added supportsVision=true directly to the kimi-coding registry entry for id "k3". That entry is the static/stable fallback catalog used when discovered capabilities are unavailable, and it must stay text-only per #4071 — the vision fix is already applied correctly on the discovered path via MODEL_SPECS["kimi-k3"] (aliases: ["k3"]) and modelCapabilities.ts::resolveVisionCapability. Restores the invariants guarded by tests/unit/kimi-k2.7-code-registration.test.ts ("Kimi Code k3 fallback leaves discovered capabilities unset") and tests/unit/catalog-updates-v3829-kimi-qwen.test.ts ("kmca stable fallback only carries documented static capabilities"). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * docs: update provider icons and enhance API interface documentation * docs: update section headers in README for improved clarity * docs: improve formatting and structure in README for better readability * feat(opencode-plugin): auto-discover models while running + force sync (#8101) * feat(opencode-plugin): auto-discover models while running + force sync Add Pi-parity discovery for OpenCode: - autoSyncIntervalMs background refresh (default 5m, min 60s, 0=off) - omniroute_sync_models tool to force cache invalidate + /v1/models refetch - /omni-sync and /omni-autosync command templates (OpenCode has no slash API) * docs(opencode-plugin): document auto model discovery + /omni-sync Document Pi-parity catalog refresh for OpenCode: - autoSyncIntervalMs background discovery (default 5m) - omniroute_sync_models force-refresh tool - /omni-sync and /omni-autosync command templates --------- Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> * chore(deps): bump next to 16.2.11 (9 security advisories) (#8265) Closes 9 Dependabot alerts (#135-#143) — Next.js 16.0.0..<16.2.11: SSRF in Server Actions/rewrites, cache confusion, DoS (Server Actions, Image Optimization SVG, Edge payload), middleware/proxy bypass, and unauthenticated Server Function endpoint disclosure. Lockfile bump within the existing ^16.2.6 range (now floored at ^16.2.11); no production code touched. Co-authored-by: rafaumeu <rafael.zendron22@gmail.com> * fix(antigravity): add missing gemini-3.6-flash pricing rows to ag OAuth pricing (#8290) release/v3.8.49 already ships the Gemini 3.6 Flash catalog entries (AGY_PUBLIC_MODELS, ANTIGRAVITY_PUBLIC_MODELS, MODEL_SPECS with supportsThinking: false — Antigravity still rejects client-supplied thinking params) via #8013. What was still missing: the `ag` pricing rows in DEFAULT_PRICING_OAUTH, so getPricingForModel("ag", id) returned null for the three tiers and cost/quota calculations silently fell back to $0. Pricing: $1.50 input / $7.50 output / $0.15 cached per MTok (Google's 2026-07-21 announcement), matching the existing 3.5-flash schedule shape. Thinking tokens billed at output rate. Extends the existing pricing-ag-flash-tiers.test.ts (RED-first: all three tiers failed the "non-null pricing row" assertion before this change) rather than re-adding the already-shipped catalog/modelSpecs entries. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * feat(combo): enforce provider and model family invariants (#8304) * feat(combo): enforce provider and model family invariants Closes #8279 Co-Authored-By: Ravi Tharuma <ravitharuma@users.noreply.github.com> * fix(combo): complete invariant enforcement paths Map invariant failures to structured API errors, correct target diagnostics, and validate restored combos inside the existing migration transaction. Co-Authored-By: Ravi Tharuma <noreply@github.com> --------- Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Ravi Tharuma <noreply@github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(memory): self-heal upsertVector/deleteVector from a raced vec_memories drop (#8337) Live incident: memory.vec.upsert.fail {"error":"no such table: vec_memories"} recurred repeatedly right after restarts, even though ensureReady() is called immediately beforehand. ensureReady()'s signature-check-then-maybe-recreate logic (resetForSignature does DROP TABLE IF EXISTS + CREATE VIRTUAL TABLE) is not synchronized against a concurrent caller's upsertVector/deleteVector -- a second in-flight memory write that independently decides (from a stale read of memory_vec_meta) it also needs to reset the table can drop it out from under another write's insert. Confirmed live: memory_vec_meta showed vec_loaded=0 for the entire session across many restarts, then flipped to 1 mid-investigation once one attempt finally completed without interruption -- consistent with an intermittent race, not a permanently broken path (verified the underlying sqlite-vec extension and CREATE VIRTUAL TABLE statement work correctly in isolation, both on the host and inside the production container). Rather than chase the exact interleaving (every underlying SQLite call is synchronous via better-sqlite3, so the race window is narrow and did not reproduce under simple Promise.all stress tests), makes the write path resilient to arriving after the table was dropped: on a "no such table" error, recreate vec_memories from the last-known-good memory_vec_meta dimension and retry once. Co-authored-by: Markus Hartung <markus.hartream@gmail.com> * fix(sse): stop stripInternalReasoningPlaceholder from eating inter-word spaces (#8341) Live incident: streamed assistant text was losing the spaces BETWEEN words (e.g. "Bilden är en riktig JPEG nu" -> "Bildenärenriktig JPEG nu") on the Responses-API and Claude streaming paths. stripInternalReasoningPlaceholder() (#8081/#8162) is called on every individual delta.content chunk, and unconditionally called .trim() even when its sentinel ("(prior reasoning summary unavailable)") was never present in that chunk. Tokenizers commonly emit sub-word tokens with a leading space as part of the token (e.g. " en", " riktig") -- each such chunk got its only whitespace character (the inter-word space) silently trimmed away before being appended to the accumulated message, while the words themselves stayed intact. Punctuation-only chunks were largely unaffected, matching what was observed live. Only trims when the sentinel is actually present -- preserves the original #8081 intent (collapse a placeholder-only chunk to "") without touching the overwhelming majority of chunks that never contain it. Co-authored-by: Markus Hartung <markus.hartream@gmail.com> * fix(dashboard): show custom provider_nodes providers in the Topology view instead of only AI_PROVIDERS (#8328) (#8357) * fix(sse): strip third-party-agent signals from the Hermes system prompt that trigger Anthropic 400 extra-usage (#8350) (#8358) * fix(api): accept the current compatible-provider connection id scheme in the models test route (#8326) (#8359) * fix(backend): keep combo routing from dispatching image requests to text-only targets (#8332) (#8360) * fix(api): stop leaking the internal provider UUID in /v1/models and honor the configured prefix (#8327) (#8361) * refactor(sse): classify SSE critical-path empty catches + add CONTRIBUTING convention (#8142) (#8364) * fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331) (#8356) * fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331) * fix(sse): scope #8331's usage-buffer fix around Claude-Code-compatible providers The #8331 fix correctly stopped folding the 2000-token context-window safety margin into client-visible prompt_tokens/input_tokens/total_tokens for normal API metering clients. But it also silently changed the response shape for Claude-Code-compatible providers, whose own context accounting reads the buffered number straight out of usage — regressing tests/unit/cc-compatible-provider.test.ts (expected 2007, got 7). Fold the computed context_budget_* fields back into the visible usage fields for that one path only (applyClientUsageBuffer's new preserveContextBudgetInVisibleUsage option, gated on the existing isClaudeCodeCompatible flag in chatCore.ts). Every other caller keeps the real, unbuffered #8331 numbers. * docs: enhance README formatting with tables for better structure and readability * docs: enhance README with tables for improved structure and readability --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: growab <nekron@icloud.com> Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com> Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com> Co-authored-by: Chirag Singhal <chirag127@users.noreply.github.com> Co-authored-by: Ronaldo Davi <ronaldodavi@gmail.com> Co-authored-by: Andrew Munsell <andrew@wizardapps.net> Co-authored-by: WITALO ROCHA <witalo_rocha@hotmail.com> Co-authored-by: Wital <witalorocha216@gmail.com> Co-authored-by: Aoxiong Yin <i@yinaoxiong.cn> Co-authored-by: Andrew B. <37745667+AndrianBalanescu@users.noreply.github.com> Co-authored-by: Andrian B. <andrewbalanesq@gmail.com> Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Co-authored-by: Jon Bailey <297513015+Pitchfork-and-Torch@users.noreply.github.com> Co-authored-by: Pitchfork-and-Torch <Pitchfork-and-Torch@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Samir Abis <me@samirabis.com> Co-authored-by: lucasjustinudin <34107354+lucasjustinudin@users.noreply.github.com> Co-authored-by: chy1211 <31048289+chy1211@users.noreply.github.com> Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com> Co-authored-by: whale9820 <whale9820@users.noreply.github.com> Co-authored-by: anhdiepmmk <n08ni.dieppn@gmail.com> Co-authored-by: Septianata Rizky Pratama <19322988+ianriizky@users.noreply.github.com> Co-authored-by: NOXX - Commiter <artur1992123@mail.ru> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: lunkerchen <labanchen@gmail.com> Co-authored-by: lunkerchen <lunkerchen@users.noreply.github.com> Co-authored-by: Ray Doan <raydoan.contact@gmail.com> Co-authored-by: kfiramar <83420275+kfiramar@users.noreply.github.com> Co-authored-by: Jan Leon <Jan.gaschler@gmail.com> Co-authored-by: Someres <168349709+quanturbo@users.noreply.github.com> Co-authored-by: MikeTuev <ra9ftm@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Imam Wahyu Widodo <120608486+hajilok@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: AgentKiller45 <jamalzzj45@gmail.com> Co-authored-by: judy459 <JUDYZHU459@outlook.com> Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: KooshaPari <koosha@phenotype.io> Co-authored-by: Jade Guo <jade.gly@gmail.com> Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com> Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: Dayna Blackwell <dayna@blackwell-systems.com> Co-authored-by: backryun <backryun@daonlab.local> Co-authored-by: brick30llc-ctrl <brick30llc@gmail.com> Co-authored-by: brick30llc-ctrl <admin@brick30.com> Co-authored-by: Saren <saren@dumstruck.com> Co-authored-by: Rafael Dias Zendron <mmmarckos@gmail.com> Co-authored-by: Xiangzhe <xiangzhedev@gmail.com> Co-authored-by: Rafael Dias Zendron <rafael.zendron22@gmail.com> Co-authored-by: KooshaPari <62650152+KooshaPari@users.noreply.github.com> Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com> Co-authored-by: huohua-dev <celentanohertor@gmail.com> Co-authored-by: huohua-dev <258873123+huohua-dev@users.noreply.github.com> Co-authored-by: CitrusIce <31264099+CitrusIce@users.noreply.github.com> Co-authored-by: minisforum <no@mail.com> Co-authored-by: Ravi Tharuma <25951435+RaviTharuma@users.noreply.github.com> Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Prudhvi Vuda <53619858+Prudhvivuda@users.noreply.github.com> Co-authored-by: Ridho Pratama <p.ridho9@gmail.com> Co-authored-by: Bob.Hou <houminxi@gmail.com> Co-authored-by: Ravi Tharuma <noreply@github.com> Co-authored-by: Markus Hartung <markus.hartream@gmail.com>
This commit is contained in:
1
changelog.d/fixes/8355-zhcn-zhtw-i18n-propernouns.md
Normal file
1
changelog.d/fixes/8355-zhcn-zhtw-i18n-propernouns.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(i18n):** Restore brand/model proper nouns (Claude, OpenAI, Anthropic, Gemini, MiniMax, etc.) in zh-CN and zh-TW — replace Chinese phonetic/translation forms (克劳德/打开Ai/人择/双子座) with original English, unify "provider" translation to "供应商/供應商", and apply zh-TW localized terminology (網路/設定/檔案/新增/啟用/搜尋/儲存) instead of mainland defaults (#8355 — thanks @ikelvingo).
|
||||
@@ -539,7 +539,7 @@
|
||||
"mcpQuickStartTitle": "MCP 快速开始",
|
||||
"comboUpdated": "Combo 已更新",
|
||||
"weighted": "加权",
|
||||
"providers": "Provider",
|
||||
"providers": "供应商",
|
||||
"ccCompatibleLabel": "CC 兼容",
|
||||
"noFallbackChainsDesc": "创建一条链路,用于定义某个模型的提供者回退顺序。",
|
||||
"yesImport": "确认导入",
|
||||
@@ -583,7 +583,7 @@
|
||||
"cacheCleared": "缓存已清除",
|
||||
"searchTypeNews": "搜索类型:News",
|
||||
"durationMillisecondsShort": "时长毫秒短标签",
|
||||
"addOpenAICompatible": "添加打开Ai兼容",
|
||||
"addOpenAICompatible": "添加OpenAI 兼容",
|
||||
"chatTesterTab": "聊天测试标签页",
|
||||
"queued": "排队中",
|
||||
"domainPlaceholder": "域名占位符",
|
||||
@@ -1444,15 +1444,15 @@
|
||||
"auth.login.success": "登录成功",
|
||||
"auth.logout.success": "注销成功",
|
||||
"compliance.cleanup": "合规清理",
|
||||
"provider.credentials.applied": "提供者凭据已应用",
|
||||
"provider.credentials.batch_revoked": "提供者凭据批次已撤销",
|
||||
"provider.credentials.bulk_created": "提供者凭据批量创建",
|
||||
"provider.credentials.bulk_imported": "提供者凭据已批量导入",
|
||||
"provider.credentials.created": "提供者凭据已创建",
|
||||
"provider.credentials.imported": "提供者凭据已导入",
|
||||
"provider.credentials.revoked": "提供者凭据已被撤销",
|
||||
"provider.credentials.updated": "提供者凭据已更新",
|
||||
"provider.validation.ssrf_blocked": "提供者 SSRF 被阻止",
|
||||
"provider.credentials.applied": "供应商凭据已应用",
|
||||
"provider.credentials.batch_revoked": "供应商凭据批次已撤销",
|
||||
"provider.credentials.bulk_created": "供应商凭据批量创建",
|
||||
"provider.credentials.bulk_imported": "供应商凭据已批量导入",
|
||||
"provider.credentials.created": "供应商凭据已创建",
|
||||
"provider.credentials.imported": "供应商凭据已导入",
|
||||
"provider.credentials.revoked": "供应商凭据已被撤销",
|
||||
"provider.credentials.updated": "供应商凭据已更新",
|
||||
"provider.validation.ssrf_blocked": "供应商 SSRF 被阻止",
|
||||
"quota.plan.updated": "配额计划已更新",
|
||||
"quota.pool.created": "配额池已创建",
|
||||
"quota.pool.deleted": "配额池已删除",
|
||||
@@ -2219,7 +2219,7 @@
|
||||
"emptyFilterTitle": "没有密钥匹配当前筛选条件",
|
||||
"emptyFilterClear": "清除筛选",
|
||||
"disableNonPublicModels": "禁用非公开模型",
|
||||
"disableNonPublicModelsDesc": "拒绝对未发现或未标记为公共的模型在提供者目录中的请求",
|
||||
"disableNonPublicModelsDesc": "拒绝对未发现或未标记为公共的模型在供应商目录中的请求",
|
||||
"normalKeysSection": "普通键",
|
||||
"quotaKeysSection": "配额密钥",
|
||||
"bypassProviderQuota": "绕过服务商配额限制",
|
||||
@@ -2871,7 +2871,7 @@
|
||||
"visibleToolsCount": "{count} 个工具可用",
|
||||
"customCliBuilderTitle": "兼容 OpenAI 的 CLI 构建器",
|
||||
"customCliBuilderDescription": "为任何接受 OpenAI 兼容的基本 URL、API 密钥和模型 ID 的 CLI 或 SDK 生成环境变量和 JSON 片段。",
|
||||
"customCliNoModels": "连接至少一个提供者以填充模型选择器。",
|
||||
"customCliNoModels": "连接至少一个供应商以填充模型选择器。",
|
||||
"customCliNameLabel": "CLI 名称",
|
||||
"customCliNamePlaceholder": "例如我的团队 CLI",
|
||||
"customCliDefaultModelLabel": "默认型号",
|
||||
@@ -2886,7 +2886,7 @@
|
||||
"customCliEndpointHintLabel": "如何连接端点",
|
||||
"customCliEndpointHint": "将任何 OpenAI 兼容客户端指向 OmniRoute /v1 基本 URL。原始聊天完成端点是 {endpoint}。当工具需要提供程序对象时使用 JSON 块,或者在读取 OPENAI_* 变量时使用 env 脚本。",
|
||||
"customCliEnvBlockTitle": "环境 / shell 片段",
|
||||
"customCliJsonBlockTitle": "提供者 JSON 块",
|
||||
"customCliJsonBlockTitle": "供应商 JSON 块",
|
||||
"networkError": "网络错误",
|
||||
"other": "其他",
|
||||
"preview": "预览",
|
||||
@@ -4086,15 +4086,15 @@
|
||||
},
|
||||
"embedding": {
|
||||
"autoLabel": "自动",
|
||||
"autoDesc": "使用最佳可用选项:远程提供者 > 静态 > 转换器",
|
||||
"remoteLabel": "远程提供者",
|
||||
"remoteDesc": "通过提供者 API 使用嵌入(需要 API 密钥)",
|
||||
"autoDesc": "使用最佳可用选项:远程供应商 > 静态 > 转换器",
|
||||
"remoteLabel": "远程供应商",
|
||||
"remoteDesc": "通过供应商 API 使用嵌入(需要 API 密钥)",
|
||||
"staticLabel": "静态本地(药水)",
|
||||
"staticDesc": "不使用WASM或外部依赖的本地嵌入",
|
||||
"transformersLabel": "Transformers.js (MiniLM)",
|
||||
"transformersDesc": "通过 @huggingface/transformers 本地嵌入 (~400MB RAM)",
|
||||
"providerModelLabel": "提供者 / 模型",
|
||||
"noRemoteProviders": "没有配置 API 密钥的提供者",
|
||||
"providerModelLabel": "供应商 / 模型",
|
||||
"noRemoteProviders": "没有配置 API 密钥的供应商",
|
||||
"selectProviderModel": "选择一个模型",
|
||||
"staticEnabledLabel": "启用静态药水",
|
||||
"staticEnabledDesc": "在本地下载并使用 potion-base-8M 模型",
|
||||
@@ -4208,9 +4208,9 @@
|
||||
"enableLabel": "启用重新排序",
|
||||
"enableDesc": "在搜索后使用重新排序模型对结果进行重新排序",
|
||||
"warning": "Rerank 会增加 +200-500ms 的延迟和每个请求的额外成本。请谨慎使用。",
|
||||
"providerModelLabel": "重新排序提供者 / 模型",
|
||||
"noProviderWithKey": "没有配置 API 密钥的提供者。请配置一个提供者以使用 rerank。",
|
||||
"selectProviderModel": "选择一个提供者/模型"
|
||||
"providerModelLabel": "重新排序供应商 / 模型",
|
||||
"noProviderWithKey": "没有配置 API 密钥的供应商。请配置一个供应商以使用 rerank。",
|
||||
"selectProviderModel": "选择一个供应商/模型"
|
||||
},
|
||||
"save": "保存",
|
||||
"saving": "保存中...",
|
||||
@@ -5228,18 +5228,18 @@
|
||||
"applyClaudeAuthLocal": "申请授权",
|
||||
"exportClaudeAuthFile": "导出授权",
|
||||
"importClaudeAuth": "导入授权",
|
||||
"claudeApplyModalTitle": "适用于当地克劳德代码",
|
||||
"claudeApplyModalTitle": "适用于当地Claude Code",
|
||||
"claudeApplyTargetLabel": "目标路径",
|
||||
"claudeApplyBackupLabel": "备份",
|
||||
"claudeApplyMcpHint": "现有的 MCP OAuth 状态将被保留。",
|
||||
"claudeApplyWarning": "这将取代现有的 claudeAiOauth 部分。继续?",
|
||||
"claudeApplyConfirmCheckbox": "我确认我想替换现有的 claudeAiOauth 部分",
|
||||
"claudeApply": "申请",
|
||||
"claudeAuthAppliedLocal": "克劳德授权在本地应用",
|
||||
"claudeAuthAppliedLocal": "Claude授权在本地应用",
|
||||
"claudeAuthApplyFailed": "本地申请Claude auth失败",
|
||||
"claudeAuthExported": "克劳德授权文件导出",
|
||||
"claudeAuthExported": "Claude授权文件导出",
|
||||
"claudeAuthExportFailed": "无法导出 Claude 身份验证文件",
|
||||
"claudeImportModalTitle": "导入克劳德·奥特",
|
||||
"claudeImportModalTitle": "导入Claude·奥特",
|
||||
"claudeImportTabSingle": "单人",
|
||||
"claudeImportTabBulk": "散装",
|
||||
"claudeImportTabUpload": "上传文件",
|
||||
@@ -5250,12 +5250,12 @@
|
||||
"claudeImportNameLabel": "连接名称(可选)",
|
||||
"claudeImportOverwriteLabel": "如果帐户已存在,则替换现有连接",
|
||||
"claudeImportSubmit": "进口",
|
||||
"claudeImportSuccess": "克劳德连接导入成功",
|
||||
"claudeImportSuccess": "Claude连接导入成功",
|
||||
"claudeImportInvalidJson": "无法将文件解析为 JSON",
|
||||
"claudeImportInvalidShape": "该文件不是有效的 .credentials.json",
|
||||
"claudeImportDuplicate": "帐户已存在 - 启用“替换现有”以覆盖",
|
||||
"claudeImportIdentityUnverified": "Bootstrap 无法验证该帐户。启用“替换现有”或提供电子邮件。",
|
||||
"claudeImportFailed": "导入克劳德授权失败",
|
||||
"claudeImportFailed": "导入Claude授权失败",
|
||||
"claudeImportBulkModeUpload": "上传文件",
|
||||
"claudeImportBulkModePaste": "粘贴 JSON 数组",
|
||||
"claudeImportBulkModeZip": "上传ZIP",
|
||||
@@ -5618,7 +5618,7 @@
|
||||
"providerDetailCallbackUrl": "回调 URL",
|
||||
"providerDetailValidClaudeCredentialsFile": "有效的 Claude 凭据文件",
|
||||
"providerDetailPathAutoDetectedAllOs": "路径按操作系统自动检测(Linux/Mac/Windows)。",
|
||||
"providerDetailMyClaudeAccountPlaceholder": "我的克劳德账户",
|
||||
"providerDetailMyClaudeAccountPlaceholder": "我的Claude 账户",
|
||||
"providerDetailPathAutoDetected": "根据操作系统 (Linux/Mac) 自动检测路径。",
|
||||
"compatBlockedParamsPlaceholder": "thinking, … (逗号分隔)",
|
||||
"compatAllowedParamsPlaceholder": "reasoning, … (逗号分隔)",
|
||||
@@ -5670,7 +5670,7 @@
|
||||
"onboardingSearchProviders": "搜索提供者...",
|
||||
"onboardingApiKeyOptional": "API 密钥可选",
|
||||
"onboardingProviderConnected": "提供者已连接",
|
||||
"onboardingProviderSavedWithWarnings": "提供者保存时带有警告",
|
||||
"onboardingProviderSavedWithWarnings": "供应商保存时带有警告",
|
||||
"onboardingProviderFinished": "提供者入职完成",
|
||||
"onboardingYourProviderConnection": "您的提供者连接",
|
||||
"onboardingTestPassed": "测试通过",
|
||||
@@ -5682,12 +5682,12 @@
|
||||
"onboardingValidatingCredentials": "正在验证凭据...",
|
||||
"onboardingSavingConnection": "正在保存提供者连接...",
|
||||
"onboardingProviderFailed": "提供者加入失败",
|
||||
"onboardingCreatingCompatibleProvider": "创建兼容的提供者...",
|
||||
"onboardingCreatingCompatibleProvider": "创建兼容的供应商...",
|
||||
"onboardingSavingCompatibleConnection": "正在保存兼容的提供者连接...",
|
||||
"onboardingCustomProviderFallbackName": "定制提供者",
|
||||
"onboardingCustomProviderFailed": "自定义提供者加入失败",
|
||||
"onboardingLoadingOAuthConnection": "正在加载 OAuth 连接...",
|
||||
"onboardingOAuthNoConnectionFound": "OAuth 已完成,但未找到提供者连接。",
|
||||
"onboardingOAuthNoConnectionFound": "OAuth 已完成,但未找到供应商连接。",
|
||||
"onboardingOAuthFailed": "OAuth 登录失败",
|
||||
"onboardingAddProvider": "添加 {provider}",
|
||||
"onboardingConnectionName": "连接名称",
|
||||
@@ -5706,7 +5706,7 @@
|
||||
"onboardingProtocol": "协议",
|
||||
"onboardingOpenAiCompatible": "兼容 OpenAI",
|
||||
"onboardingAnthropicCompatible": "人类兼容",
|
||||
"onboardingClaudeCodeCompatible": "克劳德代码兼容",
|
||||
"onboardingClaudeCodeCompatible": "Claude Code兼容",
|
||||
"onboardingProviderPrefix": "提供者前缀",
|
||||
"onboardingProviderPrefixHint": "用于生成托管提供者 ID。",
|
||||
"onboardingChatPath": "聊天路径",
|
||||
@@ -5916,9 +5916,9 @@
|
||||
"codex": "使用现有的 OAuth 流程连接 OpenAI Codex。",
|
||||
"qwen": "使用现有的 OAuth 流程连接 Qwen Code。"
|
||||
},
|
||||
"passthroughModelsDescription": "{provider} 接受提供者本机模型 ID。从 /models 导入或添加用于路由的自定义 ID。",
|
||||
"passthroughModelsDescription": "{provider} 接受供应商本机模型 ID。从 /models 导入或添加用于路由的自定义 ID。",
|
||||
"bedrockModelsDescription": "Amazon Bedrock 模型的范围按 AWS 区域划分。从 /models 导入或添加在所选区域中启用的基岩模型 ID。",
|
||||
"bedrockModelPlaceholder": "人类.克劳德十四行诗-4-6",
|
||||
"bedrockModelPlaceholder": "anthropic.Claudesonnet-4-6",
|
||||
"addProviderSessionCookieTitle": "添加 {provider} 会话 cookie",
|
||||
"openWebProviderSite": "打开 {host}",
|
||||
"addProviderWebTokenTitle": "添加 {provider} 网络令牌",
|
||||
@@ -7283,7 +7283,7 @@
|
||||
"memorySkillsSkillsmpMarketplace": "SkillsMP 市场",
|
||||
"memorySkillsFailedToSave": "保存失败",
|
||||
"memorySkillsApiKey": "API密钥",
|
||||
"memorySkillsActiveSkillsProvider": "主动技能提供者",
|
||||
"memorySkillsActiveSkillsProvider": "主动技能供应商",
|
||||
"cliproxyapiFallback": "CLIProxyAPI 后备",
|
||||
"cliproxyapiEnableFallback": "启用 CLIProxyAPI 回退",
|
||||
"cliproxyapiUrl": "CLIProxyAPI URL",
|
||||
@@ -7357,12 +7357,12 @@
|
||||
"codexFastTierModelsLabel": "快速层模型",
|
||||
"codexFastTierModelsHint": "启用快速层后,只有勾选的模型会附带 service_tier。",
|
||||
"codexFastTierModelCheckbox": "为 {model} 启用快速层",
|
||||
"claudeFastModeTitle": "克劳德快速模式",
|
||||
"claudeFastModeDesc": "选择选定的克劳德请求进入人择快速模式(速度:“快速”)。",
|
||||
"claudeFastModeTitle": "Claude快速模式",
|
||||
"claudeFastModeDesc": "选择选定的Claude请求进入Anthropic快速模式(速度:“快速”)。",
|
||||
"claudeFastModeHint": "Anthropic 并未正式支持 SDK 样式客户端的快速模式。启用后,OmniRoute 会转发 X-CPA-Force-Fast-Mode 标头,以便配对的 CLIProxyAPI 构建可以选择欺骗入口点。只有列出的 Opus 模型才会受到 Anthropic 客户端检查的控制。订阅层、最大计划和快速模式信用余额仍然在服务器端强制执行 - 即使打开切换,Anthropic 也可能返回 out_of_credits。",
|
||||
"claudeFastModeModelsLabel": "应用于模型 ({count})",
|
||||
"claudeFastModeModelCheckbox": "为 {model} 启用快速模式",
|
||||
"claudeFastModeSaveError": "无法更新克劳德快速模式设置",
|
||||
"claudeFastModeSaveError": "无法更新Claude快速模式设置",
|
||||
"authz": {
|
||||
"cors": {
|
||||
"wildcard": {
|
||||
@@ -7453,7 +7453,7 @@
|
||||
"resilienceWaitForCooldownScope": "当前客户请求",
|
||||
"resilienceWaitForCooldownTrigger": "当所有候选连接已经冷却时",
|
||||
"resilienceWaitForCooldownEffect": "等待服务器并在第一个冷却时间到期时重试",
|
||||
"resilienceWaitForCooldownDesc": "这仅影响当前请求。它不存储连接或提供者状态。",
|
||||
"resilienceWaitForCooldownDesc": "这仅影响当前请求。它不存储连接或供应商状态。",
|
||||
"resilienceEnableServerWait": "启用服务器端等待",
|
||||
"resilienceEnableServerWaitDesc": "启用后,OmniRoute 会等待第一次冷却时间到期并自动重试。",
|
||||
"resilienceMaxAttempts": "最大尝试次数",
|
||||
@@ -8323,14 +8323,14 @@
|
||||
"techniques": "技巧:",
|
||||
"friendlyTitle": "翻译器",
|
||||
"friendlySubtitle": "使用您现有的应用程序与任何提供者 — 无需重写代码。",
|
||||
"conceptHeadline": "您的应用程序使用一个 API 的“语言”。翻译器将其转换为使用另一个提供者。",
|
||||
"conceptHeadline": "您的应用程序使用一个 API 的“语言”。翻译器将其转换为使用另一个供应商。",
|
||||
"conceptDiagramAppLabel": "您的应用程序",
|
||||
"conceptDiagramSourceLabel": "源格式",
|
||||
"conceptDiagramHubLabel": "OpenAI (中心)",
|
||||
"conceptDiagramTargetLabel": "目标提供者",
|
||||
"conceptDiagramTargetLabel": "目标供应商",
|
||||
"conceptDiagramExampleApp": "例如 Anthropic SDK",
|
||||
"conceptDiagramExampleSource": "Claude",
|
||||
"conceptDiagramExampleTarget": "双子座",
|
||||
"conceptDiagramExampleTarget": "Gemini",
|
||||
"conceptHowItWorksToggle": "它是如何工作的",
|
||||
"conceptHowItWorksBody": "您的应用以其自己的格式发送请求。翻译器检测该格式,通过 OpenAI 作为中介中心进行转换(或在可用的情况下直接进行转换),将其发送到所选提供者,并将响应转换回您应用的格式。",
|
||||
"tabTranslate": "翻译",
|
||||
@@ -8340,7 +8340,7 @@
|
||||
"simpleAppUsesLabel": "我的应用程序使用",
|
||||
"simpleAppUsesHint": "您的应用程序使用的 API 格式(例如,Anthropic SDK = claude)。",
|
||||
"simpleSendToLabel": "发送到",
|
||||
"simpleSendToHint": "实际将请求发送到哪里(在 OmniRoute 中连接的提供者)。",
|
||||
"simpleSendToHint": "实际将请求发送到哪里(在 OmniRoute 中连接的供应商)。",
|
||||
"simpleStartWithLabel": "开始于",
|
||||
"simpleStartWithExamplePlaceholder": "选择一个现成的示例",
|
||||
"simpleStartWithCustomOption": "粘贴您的请求(高级)",
|
||||
@@ -8379,17 +8379,17 @@
|
||||
"pipelineStepFormatDetectedDesc": "自动检测到的源格式",
|
||||
"pipelineStepOpenAIIntermediate": "OpenAI 中级",
|
||||
"pipelineStepOpenAIIntermediateDesc": "翻译为 OpenAI hub 格式",
|
||||
"pipelineStepProviderFormat": "提供者格式",
|
||||
"pipelineStepProviderFormatDesc": "翻译为提供者目标格式",
|
||||
"pipelineStepProviderResponse": "提供者响应",
|
||||
"pipelineStepProviderResponseDesc": "来自提供者的流式响应",
|
||||
"pipelineStepProviderFormat": "供应商格式",
|
||||
"pipelineStepProviderFormatDesc": "翻译为供应商目标格式",
|
||||
"pipelineStepProviderResponse": "供应商响应",
|
||||
"pipelineStepProviderResponseDesc": "来自供应商的流式响应",
|
||||
"conceptDiagramArrow1": "说话",
|
||||
"conceptDiagramArrow2": "翻译",
|
||||
"conceptDiagramArrow3": "转换",
|
||||
"conceptDiagramExampleHub": "OpenAI",
|
||||
"conceptDiagramHubTooltip": "翻译器用于在没有直接映射的格式之间转换的中间枢纽。",
|
||||
"conceptDiagramSourceTooltip": "您的应用程序使用的 API 格式(例如,Anthropic SDK = claude)。",
|
||||
"conceptDiagramTargetTooltip": "请求将实际发送到的提供者。",
|
||||
"conceptDiagramTargetTooltip": "请求将实际发送到的供应商。",
|
||||
"compressionEmptyHint": "填写“翻译”标签页上的输入字段(简单控件或原始 JSON)以启用预览。",
|
||||
"compressionModeLabel": "压缩模式",
|
||||
"compressionPreviewButton": "预览压缩",
|
||||
@@ -10401,10 +10401,10 @@
|
||||
"kpiAvgUtilization": "平均利用率",
|
||||
"kpiBorrowingNow": "现在借款",
|
||||
"conceptTitle": "配额分成是如何工作的",
|
||||
"conceptIntro": "配额共享通过节约型公平分享将提供者的配额分配给多个 API 密钥:每个密钥获得一个按比例分配的份额,但可以在不超过全球上限的情况下从自由余额中借用。",
|
||||
"conceptIntro": "配额共享通过节约型公平分享将供应商的配额分配给多个 API 密钥:每个密钥获得一个按比例分配的份额,但可以在不超过全球上限的情况下从自由余额中借用。",
|
||||
"conceptFairShare": "公平共享:每个键接收与其配置权重成比例的配额",
|
||||
"conceptBorrowing": "借用:密钥可以在不违反上限的情况下消耗他人的自由余额",
|
||||
"conceptGlobalCap": "硬性全球上限:提供者的绝对限制永远不会被超越",
|
||||
"conceptGlobalCap": "硬性全球上限:供应商的绝对限制永远不会被超越",
|
||||
"conceptWindows": "Windows: 5小时,按小时、按日、按周、按月 — 每个独立跟踪",
|
||||
"conceptKeyHowTitle": "为配额启用密钥",
|
||||
"conceptKeyHowDesc": "在 API 管理器中正常创建密钥 — 它会自动出现在向导的密钥步骤中。在那里勾选独占以使其仅限配额。没有单独的启用步骤。",
|
||||
@@ -10429,7 +10429,7 @@
|
||||
"wizardStep2Label": "限制",
|
||||
"wizardStep3Label": "密钥",
|
||||
"wizardStep1Title": "选择提供者连接",
|
||||
"wizardStep1Subtitle": "选择此池将共享配额的提供者帐户,设置名称和默认策略。",
|
||||
"wizardStep1Subtitle": "选择此池将共享配额的供应商帐户,设置名称和默认策略。",
|
||||
"wizardStep2Title": "配置配额维度",
|
||||
"wizardStep2Subtitle": "为所选连接定义配额计划维度(单位、窗口、限制)。保持不变以保持当前设置。",
|
||||
"wizardStep3Title": "分配 API 密钥",
|
||||
@@ -10443,10 +10443,10 @@
|
||||
"wizardExclusiveLabel": "独占配额",
|
||||
"wizardExclusiveHint": "启用后,这些 API 密钥将仅允许使用此池的虚拟模型(在保存时应用 allowedQuotas 对账)。",
|
||||
"wizardPreviewLabel": "虚拟模型名称预览",
|
||||
"wizardConnectionsLabel": "提供者连接",
|
||||
"wizardConnectionsLabel": "供应商连接",
|
||||
"wizardPrimaryBadge": "主要",
|
||||
"wizardAdditionalConnectionsNote": "附加连接使用其目录默认限制(稍后可编辑)。",
|
||||
"wizardSingleProviderNote": "一个池使用单一提供者",
|
||||
"wizardSingleProviderNote": "一个池使用单一供应商",
|
||||
"wizardPreviewMoreModels": "+{count} 更多",
|
||||
"accountQuotaTitle": "账户配额",
|
||||
"accountQuotaNone": "—",
|
||||
@@ -10528,7 +10528,7 @@
|
||||
"quotaPlans": {
|
||||
"title": "计划与配额",
|
||||
"description": "为每个提供者配置配额计划 — 维度(%、请求、令牌、$)和时间窗口",
|
||||
"providerLabel": "提供者 / 连接",
|
||||
"providerLabel": "供应商 / 连接",
|
||||
"detectedPlanLabel": "检测到的计划",
|
||||
"manualPlanLabel": "手动覆盖",
|
||||
"unconfiguredLabel": "未配置 — 需要手动设置",
|
||||
@@ -10560,11 +10560,11 @@
|
||||
"title": "活动",
|
||||
"description": "最近事件动态",
|
||||
"emptyTitle": "尚无活动",
|
||||
"emptyDescription": "当您添加提供者、创建组合或旋转密钥时,事件将出现在这里。",
|
||||
"emptyDescription": "当您添加供应商、创建组合或旋转密钥时,事件将出现在这里。",
|
||||
"todayHeader": "今天",
|
||||
"yesterdayHeader": "昨天",
|
||||
"filterAll": "全部",
|
||||
"filterProviders": "提供者",
|
||||
"filterProviders": "供应商",
|
||||
"filterCombos": "组合",
|
||||
"filterApiKeys": "API 密钥",
|
||||
"filterSettings": "设置",
|
||||
@@ -10585,9 +10585,9 @@
|
||||
"daysAgo": "{n} 天前"
|
||||
},
|
||||
"eventVerb": {
|
||||
"providerAdded": "{actor} 添加了提供者 {target}",
|
||||
"providerRemoved": "{actor} 移除了提供者 {target}",
|
||||
"providerTested": "{actor} 测试了提供者 {target}",
|
||||
"providerAdded": "{actor} 添加了供应商 {target}",
|
||||
"providerRemoved": "{actor} 移除了供应商 {target}",
|
||||
"providerTested": "{actor} 测试了供应商 {target}",
|
||||
"comboCreated": "{actor} 创建了组合 {target}",
|
||||
"comboUpdated": "{actor} 更新了组合 {target}",
|
||||
"comboDeleted": "{actor} 移除了组合 {target}",
|
||||
@@ -10698,8 +10698,8 @@
|
||||
"toggling": "切换中…",
|
||||
"viewTraffic": "查看流量",
|
||||
"emptyNoProvidersTitle": "尚未配置提供程序",
|
||||
"emptyNoProvidersBody": "要使用 AgentBridge,首先连接至少一个提供者。它将是 IDE 请求路由的目标。",
|
||||
"emptyGoToProviders": "前往提供者",
|
||||
"emptyNoProvidersBody": "要使用 AgentBridge,首先连接至少一个供应商。它将是 IDE 请求路由的目标。",
|
||||
"emptyGoToProviders": "前往供应商",
|
||||
"wizardTitle": "设置向导",
|
||||
"wizardSubtitle": "3步设置",
|
||||
"wizardStep1Label": "验证",
|
||||
@@ -10879,7 +10879,7 @@
|
||||
"sessionsDropdown": "会话",
|
||||
"annotationPlaceholder": "添加备注…",
|
||||
"contextFingerprint": "上下文指纹",
|
||||
"llmProvider": "检测到的提供者",
|
||||
"llmProvider": "检测到的供应商",
|
||||
"llmApiKind": "API 类型",
|
||||
"llmModel": "模型",
|
||||
"llmMessages": "消息",
|
||||
@@ -10959,13 +10959,13 @@
|
||||
"code": {
|
||||
"title": "CLI 代码的",
|
||||
"phrase": "指向 OmniRoute 的代码工具",
|
||||
"flow": "你 → CLI 代码 → OmniRoute → 提供者",
|
||||
"flow": "你 → CLI 代码 → OmniRoute → 供应商",
|
||||
"seeOther": "查看 →"
|
||||
},
|
||||
"agent": {
|
||||
"title": "CLI 代理",
|
||||
"phrase": "通用自主CLI代理,您可以指向OmniRoute",
|
||||
"flow": "您 → CLI 代理 → OmniRoute → 提供者",
|
||||
"flow": "您 → CLI 代理 → OmniRoute → 供应商",
|
||||
"seeOther": "查看 →"
|
||||
},
|
||||
"acp": {
|
||||
@@ -10982,7 +10982,7 @@
|
||||
"code": {
|
||||
"title": "代码工具",
|
||||
"desc": "指向 Omni",
|
||||
"flow": "你 → CLI → Omni → 提供者",
|
||||
"flow": "你 → CLI → Omni → 供应商",
|
||||
"examples": "例如:claude,codex"
|
||||
},
|
||||
"agent": {
|
||||
@@ -11027,9 +11027,9 @@
|
||||
"baseUrlLabel": "基础 URL",
|
||||
"apiKeyLabel": "API 密钥",
|
||||
"modelMappingLabel": "模型映射",
|
||||
"noActiveProviders": "没有活动的提供者。",
|
||||
"noActiveProvidersDesc": "请前往 Providers 连接至少 1 个提供者,然后再配置 CLI。",
|
||||
"openProviders": "打开提供者 →"
|
||||
"noActiveProviders": "没有活动的供应商。",
|
||||
"noActiveProvidersDesc": "请前往 Providers 连接至少 1 个供应商,然后再配置 CLI。",
|
||||
"openProviders": "打开供应商 →"
|
||||
}
|
||||
},
|
||||
"cliCode": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user