From 48754fd9997871aa1b8876e04235048e2ccb1ee3 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Fri, 20 Mar 2026 12:57:08 -0300 Subject: [PATCH] =?UTF-8?q?release:=20v2.8.7=20=E2=80=94=20Bottleneck=2042?= =?UTF-8?q?9=20drop=20(PR=20#495),=20custom=20embedding=20provider=20fix?= =?UTF-8?q?=20(#496)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 18 +++++++++++++++ docs/openapi.yaml | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/app/api/v1/embeddings/route.ts | 36 +++++++++++++++++++++++++++--- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46fc4bd6b4..7992aa32c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ --- +## [2.8.7] — 2026-03-20 + +> Sprint: Merge PR #495 (Bottleneck 429 drop), fix #496 (custom embedding providers), triage features. + +### Bug Fixes + +- **Bottleneck 429 infinite wait** (PR #495 by @xandr0s): On 429, `limiter.stop({ dropWaitingJobs: true })` immediately fails all queued requests so upstream callers can trigger fallback. Limiter is deleted from Map so next request creates a fresh instance. +- **Custom embedding models unresolvable** (#496): `POST /v1/embeddings` now resolves custom embedding models from ALL provider_nodes (not just localhost). Enables models like `google/gemini-embedding-001` added via dashboard. + +### Issues Responded + +- **#452** — Per-API-key request-count limits (acknowledged, on roadmap) +- **#464** — Auto-issue API keys with provider/account limits (needs more detail) +- **#488** — Auto-update model lists (acknowledged, on roadmap) +- **#496** — Custom embedding provider resolution (fixed) + +--- + ## [2.8.6] — 2026-03-20 > Sprint: Merge PR #494 (MiniMax role fix), fix KIRO MITM dashboard, triage 8 issues. diff --git a/docs/openapi.yaml b/docs/openapi.yaml index b192f98885..2790b17a25 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: OmniRoute API - version: 2.8.6 + version: 2.8.7 description: | OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible endpoint that routes requests to multiple AI providers with load balancing, diff --git a/package-lock.json b/package-lock.json index d3640cfbcf..eb665feefb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omniroute", - "version": "2.8.6", + "version": "2.8.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omniroute", - "version": "2.8.6", + "version": "2.8.7", "hasInstallScript": true, "license": "MIT", "workspaces": [ diff --git a/package.json b/package.json index 711deb32a8..2f809f0d16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omniroute", - "version": "2.8.6", + "version": "2.8.7", "description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.", "type": "module", "bin": { diff --git a/src/app/api/v1/embeddings/route.ts b/src/app/api/v1/embeddings/route.ts index 515cf2e28c..99c439cb05 100644 --- a/src/app/api/v1/embeddings/route.ts +++ b/src/app/api/v1/embeddings/route.ts @@ -12,6 +12,7 @@ import { getEmbeddingProvider, buildDynamicEmbeddingProvider, type EmbeddingProviderNodeRow, + type EmbeddingProvider, } from "@omniroute/open-sse/config/embeddingRegistry.ts"; import { errorResponse } from "@omniroute/open-sse/utils/error.ts"; import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts"; @@ -116,9 +117,9 @@ export async function POST(request) { // Load local provider_nodes for embedding routing (only localhost — prevents auth bypass/SSRF) let dynamicProviders: ReturnType[] = []; try { - const nodes = await getProviderNodes(); + const nodes = (await getProviderNodes()) as unknown as EmbeddingProviderNodeRow[]; dynamicProviders = (Array.isArray(nodes) ? nodes : []) - .filter((n: EmbeddingProviderNodeRow) => { + .filter((n) => { // provider_nodes apiType is "chat" or "responses" (not "embeddings") — local OpenAI-compatible // backends expose /embeddings under the same base URL as chat, so we build the URL as baseUrl + /embeddings. if (n.apiType !== "chat" && n.apiType !== "responses") return false; @@ -157,9 +158,38 @@ export async function POST(request) { } // Resolve provider config — dynamic first (local override), then hardcoded - const providerConfig = + let providerConfig: EmbeddingProvider | null = dynamicProviders.find((dp) => dp.id === provider) || getEmbeddingProvider(provider) || null; + // #496: Fallback — resolve from ALL provider_nodes (not just localhost) + // This enables custom embedding models (e.g. google/gemini-embedding-001) whose + // providers have remote baseUrls. Safe because getProviderCredentials() authenticates. + if (!providerConfig) { + try { + const allNodes = (await getProviderNodes()) as unknown as EmbeddingProviderNodeRow[]; + const matchingNode = (Array.isArray(allNodes) ? allNodes : []).find( + (n) => + n.prefix === provider && (n.apiType === "chat" || n.apiType === "responses") && n.baseUrl + ); + if (matchingNode) { + const baseUrl = String(matchingNode.baseUrl).replace(/\/+$/, ""); + providerConfig = { + id: matchingNode.prefix, + baseUrl: `${baseUrl}/embeddings`, + authType: "apikey", + authHeader: "bearer", + models: [], + }; + log.info( + "EMBED", + `Resolved custom embedding provider: ${provider} → ${providerConfig.baseUrl}` + ); + } + } catch (err) { + log.error("EMBED", `Failed to resolve custom embedding provider ${provider}: ${err}`); + } + } + if (!providerConfig) { return errorResponse( HTTP_STATUS.BAD_REQUEST,