release: v2.8.7 — Bottleneck 429 drop (PR #495), custom embedding provider fix (#496)

This commit is contained in:
diegosouzapw
2026-03-20 12:57:08 -03:00
parent c496ebdef9
commit 48754fd999
5 changed files with 55 additions and 7 deletions

View File

@@ -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.

View File

@@ -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,

4
package-lock.json generated
View File

@@ -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": [

View File

@@ -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": {

View File

@@ -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<typeof buildDynamicEmbeddingProvider>[] = [];
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,