From ccbfca84ddc45fa050a25ed23446b75d38c2ea6a Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 1 Jul 2026 01:00:11 -0300 Subject: [PATCH] fix: repoint DashScope/Alibaba setup links to consoles (#5665) (#5762) --- CHANGELOG.md | 6 +++-- .../constants/providers/apikey/regional.ts | 4 +-- tests/unit/provider-setup-links-5572.test.ts | 27 ++++++++++++++++--- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e93a060292..3f658cf468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - **mcp (RTK):** expose the RTK tool-output **learn/discover** workflow as two new MCP tools so an agent can grow the RTK filter catalog without leaving the protocol. `omniroute_rtk_discover` analyzes recently captured raw tool output (`discoverRepeatedNoise` / `suggestFilter`) and returns candidate noise patterns plus a suggested filter; `omniroute_rtk_learn` lists the captured command samples (`listRtkCommandSamples`) and resolves a command to its RTK filter id (`commandToId`). Both are read-only (scope `read:compression`), wrap the existing RTK discovery primitives (no new logic in the engine), and log to the MCP audit trail. Regression guard: `tests/unit/compression/rtk-mcp-tools.test.ts` (4). gaps v3.8.42 — T07. -- **compression (LLM tier):** add an **opt-in, default-off LLM-tier compression engine** (`llm`) that condenses the prose of non-system messages via a pluggable chat-completion backend. It mirrors the `llmlingua` engine's contract but is **safe by construction**: the default backend is a **no-op pass-through** (the engine never mutates the payload until an operator both enables it *and* wires a real backend via `setLlmCompressorBackend()`), it is **not** part of the default stacked pipeline, `enabled` defaults to `false`, fenced code blocks and `system` messages are never sent to the model, and every backend error **fails open** (the original segment/body is kept, never thrown). A `minTokens` floor skips small prompts. The real production backend is intentionally a VPS-validated follow-up (Hard Rule #18), exactly as the `llmlingua` worker backend is gated. New `open-sse/services/compression/engines/llm/index.ts`. Regression guard: `tests/unit/compression/llm-compressor-engine.test.ts` (8). gaps v3.8.42 — T05/C3. +- **compression (LLM tier):** add an **opt-in, default-off LLM-tier compression engine** (`llm`) that condenses the prose of non-system messages via a pluggable chat-completion backend. It mirrors the `llmlingua` engine's contract but is **safe by construction**: the default backend is a **no-op pass-through** (the engine never mutates the payload until an operator both enables it _and_ wires a real backend via `setLlmCompressorBackend()`), it is **not** part of the default stacked pipeline, `enabled` defaults to `false`, fenced code blocks and `system` messages are never sent to the model, and every backend error **fails open** (the original segment/body is kept, never thrown). A `minTokens` floor skips small prompts. The real production backend is intentionally a VPS-validated follow-up (Hard Rule #18), exactly as the `llmlingua` worker backend is gated. New `open-sse/services/compression/engines/llm/index.ts`. Regression guard: `tests/unit/compression/llm-compressor-engine.test.ts` (8). gaps v3.8.42 — T05/C3. - **memory (typed decay):** add **opt-in typed memory decay** (TV6) so the conversational memory store stops accumulating stale `episodic` noise. Each injected memory now tracks an `access_count` + `last_accessed_at` (always-on, non-destructive telemetry; migration `111_memory_typed_decay`), and an **opt-in, default-off** sweep (`MEMORY_TYPED_DECAY_ENABLED`, default `false`) deletes memories that are past a **per-type TTL** and not immune. Only `episodic` decays by default (30d, env-tunable); `factual`/`procedural`/`semantic` are immune, and any memory accessed `>= 3` times earns access immunity (mirroring "guardrail/convention/decision never decay"). The decay clock re-bases on the last access, so used memories survive. Deletions reuse `deleteMemory` (SQLite + sqlite-vec + Qdrant stay in sync) and fail open; an optional periodic sweep is doubly opt-in (also needs `MEMORY_TYPED_DECAY_SWEEP_INTERVAL>0`). With the flag off nothing is ever deleted (Rule #20 spirit). New `src/lib/memory/typedDecay.ts`. Regression guard: `tests/unit/memory/typed-decay.test.ts` (15). gaps v3.8.42 — T10/TV6. @@ -24,12 +24,14 @@ - **compression (CCR):** the CCR retrieval-feedback (H8) is now **graduated** instead of a binary cliff. Previously a block retrieved `>= 3` times was flagged do-not-compress and everything below that stayed fully compressible. Now each prior retrieval raises a block's **effective `minChars` linearly** (`effectiveMinChars`), so frequently-retrieved content is compressed progressively less; the `>= 3` exclusion is preserved (as `Infinity`). The ramp is controlled by a `retrievalRampFactor` (default `2`, per-combo config or `COMPRESSION_CCR_RETRIEVAL_RAMP_FACTOR`); `1` reproduces the exact legacy binary behavior. Per-`(principal, hash)` isolation is unchanged. Regression guard: `tests/unit/compression/ccr-retrieval-ramp.test.ts` (12); existing CCR suites (51) stay green. gaps v3.8.42 — T08/H8. -- **compression (cache-aware):** add an **opt-in, default-off usage-observed prefix freeze** (H5). The cache-aware guard previously preserved the system prompt only for providers a *static* heuristic recognized as caching. It now also **learns** which system prompts actually recur: once a system prompt has been observed `>=` a threshold across requests, it is treated as a stable cacheable prefix and preserved from compression **even for providers the static check misses** — recovering prompt-cache hits that a prefix-compressing mode would otherwise bust. Content-addressed by a hash of the system prompt (OpenAI / Claude / Gemini shapes), in-memory + bounded, zero DB/IO; a "freeze" only *preserves* the prefix, so it never mutates a payload. Default OFF (`COMPRESSION_PREFIX_FREEZE_ENABLED`, threshold `_THRESHOLD`); respects the `never` preserve-mode (never freezes). New `open-sse/services/compression/prefixFreeze.ts`, wired into `resolveCacheAwareConfig`. Regression guard: `tests/unit/compression/prefix-freeze.test.ts` (10); 44 existing cache-aware / preserve-mode tests stay green. gaps v3.8.42 — T08/H5. +- **compression (cache-aware):** add an **opt-in, default-off usage-observed prefix freeze** (H5). The cache-aware guard previously preserved the system prompt only for providers a _static_ heuristic recognized as caching. It now also **learns** which system prompts actually recur: once a system prompt has been observed `>=` a threshold across requests, it is treated as a stable cacheable prefix and preserved from compression **even for providers the static check misses** — recovering prompt-cache hits that a prefix-compressing mode would otherwise bust. Content-addressed by a hash of the system prompt (OpenAI / Claude / Gemini shapes), in-memory + bounded, zero DB/IO; a "freeze" only _preserves_ the prefix, so it never mutates a payload. Default OFF (`COMPRESSION_PREFIX_FREEZE_ENABLED`, threshold `_THRESHOLD`); respects the `never` preserve-mode (never freezes). New `open-sse/services/compression/prefixFreeze.ts`, wired into `resolveCacheAwareConfig`. Regression guard: `tests/unit/compression/prefix-freeze.test.ts` (10); 44 existing cache-aware / preserve-mode tests stay green. gaps v3.8.42 — T08/H5. - **compression (read-lifecycle):** add a new **opt-in, default-off `read-lifecycle` engine** (H7) that collapses **stale/superseded file-Read tool results**. In agentic conversations the same file is Read repeatedly; an earlier Read becomes stale once the same path is **re-read** (superseded by a newer view) or **modified** by a later Write/Edit. The engine replaces those earlier Read results with a short stub — keeping only the current (last, un-superseded) Read intact — recovering the tokens the model no longer needs. Unlike `session-dedup` (identical-content) or `ccr` (reversible markers), this is semantic + **lossy**, so it is opt-in (`enabled` defaults `false`). Conservative by construction: matches only well-known Read/Write tool names, compares exact paths, collapses a Read only when a strictly-later invocation touches the same path, and **fail-opens** on any unexpected shape. Supports both the **Anthropic** (`tool_use`/`tool_result`) and **OpenAI** (`tool_calls` + `role:"tool"`) shapes. New `open-sse/services/compression/engines/readLifecycle/index.ts`. Regression guard: `tests/unit/compression/read-lifecycle.test.ts` (10). gaps v3.8.42 — T08/H7. ### 🔧 Bug Fixes +- **providers (DashScope/Alibaba setup link):** the "Get API key" link for the **Alibaba** and **Alibaba (China)** providers pointed at the bare API host (`dashscope-intl.aliyuncs.com` / `dashscope.aliyuncs.com`), which returns **404** in a browser — API hostnames have no homepage. Repointed to the consoles where keys are actually issued: `bailian.console.alibabacloud.com` (international) and `dashscope.console.aliyun.com` (China). Same class as #5572/#5574/#5576; regression guard added to `tests/unit/provider-setup-links-5572.test.ts`. ([#5665](https://github.com/diegosouzapw/OmniRoute/issues/5665)) + - **thinking / runtime-config (module-graph fix):** operator-configured proxy settings that are hydrated at **boot** but read **per-request** were silently ignored in production. Next.js compiles `instrumentation.ts` (boot hydration via `applyRuntimeSettings` / restore hooks) as a **separate webpack module graph** from the app-route / open-sse executors, so a module-local `let _config` singleton is **duplicated** — the boot copy is hydrated but the request path reads a different, un-hydrated copy. Live VPS validation proved the Thinking-Budget hydration ran to completion at boot yet `base.ts` still saw the `passthrough` default (this is why #5312 fix A stayed broken even after the boot-wiring fix). Fixed by backing the singletons with `globalThis` (the pattern `systemPrompt.ts` already uses for the Global System Prompt, #2470), so all module-graph copies share one instance: **`thinkingBudget.ts`** (the dashboard Thinking-Budget mode now reaches the executor), **`backgroundTaskDetector.ts`** (the opt-in background-model degradation now actually fires on requests), and **`systemTransforms.ts`** (operator pipeline overrides now reach the request path). `payloadRules.ts` was already safe (it lazily self-loads from the DB per request, #2986). Regression guards: `tests/unit/thinking-budget-globalthis-5312.test.ts` + `tests/unit/runtime-config-globalthis-5312.test.ts` (assert globalThis-backed sharing; a module-local `let` fails them). ([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312)) - **thinking (Claude OAuth):** restore the proxy-level **Thinking-Budget** config on startup. The dashboard mode (`auto`/`custom`/`adaptive`) is persisted under `settings.thinkingBudget`, but the boot-time hydration (`hydrateThinkingBudgetConfig`) was only wired into `src/server-init.ts` — an **unused module that never runs in production** — so the operator's choice silently reverted to the `passthrough` default on every restart (#5312 fix A was non-functional, even though its direct unit test passed). The hydration now runs in the real boot path (`src/instrumentation-node.ts`), alongside the Global System Prompt restore. Surfaced by live Anthropic-OAuth validation on the VPS. Regression guard: `tests/unit/thinking-budget-boot-wiring-5312.test.ts` (asserts the production boot module calls the hydration, not just the function in isolation). ([#5312](https://github.com/diegosouzapw/OmniRoute/issues/5312)) diff --git a/src/shared/constants/providers/apikey/regional.ts b/src/shared/constants/providers/apikey/regional.ts index 3c1dc808fb..2ad534cfc0 100644 --- a/src/shared/constants/providers/apikey/regional.ts +++ b/src/shared/constants/providers/apikey/regional.ts @@ -115,7 +115,7 @@ export const APIKEY_PROVIDERS_REGIONAL = { icon: "cloud_queue", color: "#FF6600", textIcon: "AL", - website: "https://dashscope-intl.aliyuncs.com", + website: "https://bailian.console.alibabacloud.com/", hasFree: false, }, "alibaba-cn": { @@ -125,7 +125,7 @@ export const APIKEY_PROVIDERS_REGIONAL = { icon: "cloud_queue", color: "#FF6600", textIcon: "AL", - website: "https://dashscope.aliyuncs.com", + website: "https://dashscope.console.aliyun.com/", hasFree: false, }, longcat: { diff --git a/tests/unit/provider-setup-links-5572.test.ts b/tests/unit/provider-setup-links-5572.test.ts index 3c1c215370..d97738520e 100644 --- a/tests/unit/provider-setup-links-5572.test.ts +++ b/tests/unit/provider-setup-links-5572.test.ts @@ -5,9 +5,10 @@ import assert from "node:assert/strict"; // This guards the corrected (verified-live) URLs so the dead paths cannot // silently return. const { SEARCH_PROVIDERS } = await import("../../src/shared/constants/providers/search.ts"); -const { APIKEY_PROVIDERS_INFERENCE } = await import( - "../../src/shared/constants/providers/apikey/inference-hosts.ts" -); +const { APIKEY_PROVIDERS_INFERENCE } = + await import("../../src/shared/constants/providers/apikey/inference-hosts.ts"); +const { APIKEY_PROVIDERS_REGIONAL } = + await import("../../src/shared/constants/providers/apikey/regional.ts"); const websites = () => [ @@ -20,7 +21,7 @@ const websites = () => test("#5572/#5574/#5576 provider setup links avoid the known-404 paths", () => { const deadPaths = [ "ollama.com/settings/api-keys", // #5572 — Ollama key page moved to /settings/keys - "searchapi.io/docs\"", // #5574 — bare /docs 404s; use /docs/google + 'searchapi.io/docs"', // #5574 — bare /docs 404s; use /docs/google "you.com/docs/search/overview", // #5576 — moved to /business/api/ ]; for (const dead of deadPaths) { @@ -38,3 +39,21 @@ test("#5572/#5574/#5576 corrected provider setup links", () => { assert.equal(search["ollama-search"]?.website, "https://ollama.com/settings/keys"); assert.equal(inference["ollama-cloud"]?.website, "https://ollama.com/settings/keys"); }); + +// #5665 — DashScope/Alibaba "Get API key" links pointed at the bare API host +// (dashscope[-intl].aliyuncs.com), which returns 404 in a browser. Keys are +// issued from the Model Studio / DashScope consoles. Same class as #5572/#5574/#5576. +test("#5665 DashScope/Alibaba setup links use the console, not the bare API host", () => { + const regional = APIKEY_PROVIDERS_REGIONAL as Record; + const deadHosts = ["dashscope-intl.aliyuncs.com", "dashscope.aliyuncs.com"]; + for (const dead of deadHosts) { + for (const p of Object.values(regional)) { + assert.ok( + !(typeof p.website === "string" && p.website.includes(dead)), + `regional provider link still points at the bare API host: ${p.website}` + ); + } + } + assert.equal(regional["alibaba"]?.website, "https://bailian.console.alibabacloud.com/"); + assert.equal(regional["alibaba-cn"]?.website, "https://dashscope.console.aliyun.com/"); +});