diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bba532ec2..1fe49dcb30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral - **Kiro long-lived API key auth**: new `/api/oauth/kiro/api-key` route + `KiroService.validateApiKey` let a Kiro account be linked with a long-lived AWS CodeWhisperer/Kiro API key instead of the interactive OAuth device flow, with live per-account model discovery (`ListAvailableModels`, 5-minute cache) layered over the existing static registry fallback (#6587 — thanks @strangersp) - **Chaos Mode**: multi-model parallel/collaborative task execution — dispatches a task to every active provider connection at once (parallel) or chains outputs sequentially so each model builds on the previous one's answer (collaborative), configurable via Dashboard → Chaos Mode (`GET`/`PUT`/`DELETE /api/chaos/config`) and gated per-API-key via a new `chaosModeEnabled` permission (opt-in — disabled by default globally and per key). `POST /api/chaos/run` (dashboard session) and `POST /api/skills/collect/chaos` (external Bearer-token) delegate to a shared `executeChaosRun()` engine (`src/lib/chaos/chaosExecutor.ts`) that dispatches in-process via the established synthetic-Request/route-handler pattern (no network hop, no hardcoded port), with a concurrency cap (max 10 parallel), configurable `max_tokens` (256–128k), a clear error when `stream` is requested, and collaborative-chain info (provider order + input size). Fixes external Bearer-auth bypass and stale config-cache leakage. Regression guard: `tests/unit/chaos-config.test.ts`, `tests/unit/chaos-executor.test.ts`, `tests/unit/chaos-api-routes.test.ts`. ([#6728](https://github.com/diegosouzapw/OmniRoute/pull/6728) — thanks @Moseyuh333) - **feat(cli):** 2 new CLI tool integrations on Dashboard → CLI Tools — **omp** (Oh My Pi) and **letta** — each with binary detection, config apply/reset, and a settings card following the existing tool-card pattern. Both settings routes shell out to `which omp`/`which letta` to detect the local install, so they're loopback-gated (`LOCAL_ONLY_API_PREFIXES`, Hard Rules #15/#17) in addition to the shared `requireCliToolsAuth()` management-auth guard every cli-tools route requires, and route errors through `sanitizeErrorMessage()`; `src/lib/db/omp.ts` isolates the `omp` CLI's own local SQLite reads behind parameterized queries. (Note: the original PR also proposed **pi**, **codewhale**, and **jcode** integrations — those three had already shipped via a separate PR by the time this one was reconciled, so only omp+letta landed here.) Regression guard: `tests/unit/db/omp.test.ts`, `tests/unit/cli-tools-auth-hardening.test.ts`, `tests/integration/cli-settings-omp.test.ts`, `tests/integration/cli-settings-letta.test.ts`. ([#6318](https://github.com/diegosouzapw/OmniRoute/pull/6318) — thanks @hamsa0x7) +- **fix(providers):** register OpenRouter as a rerank provider so `openrouter/cohere/rerank-*` models resolve instead of erroring `Invalid rerank model` (#6574 — thanks @rafpigna) ### 🐛 Bug Fixes diff --git a/open-sse/config/rerankRegistry.ts b/open-sse/config/rerankRegistry.ts index e44efb16e1..7b8a79801c 100644 --- a/open-sse/config/rerankRegistry.ts +++ b/open-sse/config/rerankRegistry.ts @@ -89,6 +89,24 @@ export const RERANK_PROVIDERS = { ], }, + // OpenRouter exposes a separate, Cohere-compatible POST /api/v1/rerank endpoint + // (not surfaced by its live /v1/models feed, which contains 0 rerank ids — confirmed + // by direct curl). Model IDs keep their vendor slash (e.g. "cohere/rerank-4-pro"); + // parseRerankModel splits on the FIRST slash, so 3-segment ids resolve safely, same + // as siliconflow above. Seeded by hand and must be maintained here as OpenRouter adds + // more rerank models (#6574). + openrouter: { + id: "openrouter", + baseUrl: "https://openrouter.ai/api/v1/rerank", + authType: "apikey", + authHeader: "bearer", + models: [ + { id: "cohere/rerank-4-pro", name: "Cohere Rerank 4 Pro (via OpenRouter)" }, + { id: "cohere/rerank-4-fast", name: "Cohere Rerank 4 Fast (via OpenRouter)" }, + { id: "cohere/rerank-v3.5", name: "Cohere Rerank v3.5 (via OpenRouter)" }, + ], + }, + // DeepInfra rerank is NOT Cohere-shaped: POST /v1/inference/ with {queries:[q],documents} // returning {scores:[…]} (one score per document, positional). The `deepinfra` format adapter in // open-sse/handlers/rerank.ts builds the per-model URL and maps scores → Cohere results (#5332). diff --git a/tests/unit/rerank-openrouter-6574.test.ts b/tests/unit/rerank-openrouter-6574.test.ts new file mode 100644 index 0000000000..55128b1ad6 --- /dev/null +++ b/tests/unit/rerank-openrouter-6574.test.ts @@ -0,0 +1,32 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { parseRerankModel, getRerankProvider, getAllRerankModels } = await import( + "../../open-sse/config/rerankRegistry.ts" +); + +// #6574 — OpenRouter now exposes a Cohere-compatible /api/v1/rerank endpoint +// (confirmed live: openrouter.ai/cohere/rerank-4-pro, model ids stay +// fully-qualified "cohere/rerank-4-pro"), but RERANK_PROVIDERS has no +// "openrouter" entry at all. Same failure class as #5332 (siliconflow/deepinfra): +// parseRerankModel() can't resolve a provider for a 3-segment id when the +// provider itself isn't registered, so /v1/rerank falls through straight to +// the generic "Invalid rerank model" 400 without ever calling upstream. +test("#6574 parseRerankModel resolves openrouter multi-slash rerank model id", () => { + assert.deepEqual(parseRerankModel("openrouter/cohere/rerank-4-pro"), { + provider: "openrouter", + model: "cohere/rerank-4-pro", + }); +}); + +test("#6574 getRerankProvider('openrouter') returns a provider config", () => { + assert.ok(getRerankProvider("openrouter"), "openrouter should be a registered rerank provider"); +}); + +test("#6574 getAllRerankModels lists openrouter reranker models", () => { + const ids = getAllRerankModels().map((m) => m.id); + assert.ok( + ids.includes("openrouter/cohere/rerank-4-pro"), + `expected openrouter/cohere/rerank-4-pro in ${JSON.stringify(ids)}` + ); +});