fix(api): name working OpenRouter ids when Gemini embed creds are missing (#10565)

Native gemini-embedding-2 400s with a dead-end credentials error even though
openrouter/google/gemini-embedding-2 already serves 3072-d vectors.

Co-authored-by: Ravi Tharuma <RaviTharuma@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ravi Tharuma
2026-08-18 15:52:16 +02:00
committed by GitHub
parent 458ab1aac0
commit 134ab8cabb
4 changed files with 95 additions and 2 deletions

View File

@@ -0,0 +1 @@
- **fix(api):** `/v1/embeddings` 400s for native `gemini-embedding-2` now name the working OpenRouter ids (`openrouter/google/gemini-embedding-2` and the preview alias) instead of only `No credentials for embedding provider: gemini` — thanks @RaviTharuma

View File

@@ -0,0 +1,50 @@
/**
* Operator-facing embedding credential / provider errors.
*
* Native `gemini-embedding-2` is registered, but a host without a `gemini`
* Google AI Studio key still 400s. OpenRouter already serves the same model
* under `openrouter/google/gemini-embedding-2` (live default 3072-d). Name
* that working id in the 400 so Hindsight / Memorix operators are not stuck
* on a dead native id.
*/
export const OPENROUTER_GEMINI_EMBEDDING_2 = "openrouter/google/gemini-embedding-2";
export const OPENROUTER_GEMINI_EMBEDDING_2_PREVIEW =
"openrouter/google/gemini-embedding-2-preview";
export const NATIVE_GEMINI_EMBEDDING_2 = "gemini/gemini-embedding-2";
const OPENROUTER_HINT =
`use \`${OPENROUTER_GEMINI_EMBEDDING_2}\` or \`${OPENROUTER_GEMINI_EMBEDDING_2_PREVIEW}\` ` +
"when OpenRouter is configured";
/**
* 400 body when the resolved embedding provider has no usable credentials.
*/
export function formatMissingEmbeddingCredentialsError(provider: string): string {
const base = `No credentials for embedding provider: ${provider}`;
if (provider === "gemini") {
return (
`${base}. Native Gemini embeddings require a Google AI Studio API key on the \`gemini\` ` +
`provider. Without that key, ${OPENROUTER_HINT}.`
);
}
return base;
}
/**
* 400 body when the first path segment is not a known embedding provider.
*/
export function formatUnknownEmbeddingProviderError(
provider: string,
model: string | null = null
): string {
const base =
`Unknown embedding provider: ${provider}. No matching hardcoded or local provider found.`;
const looksLikeGeminiEmbedding2 =
provider === "google" || (typeof model === "string" && model.includes("gemini-embedding-2"));
if (!looksLikeGeminiEmbedding2) return base;
return (
`${base} For Gemini Embedding 2, ${OPENROUTER_HINT}, or call \`${NATIVE_GEMINI_EMBEDDING_2}\` ` +
"after adding a Google AI Studio key on the `gemini` provider."
);
}

View File

@@ -22,6 +22,10 @@ import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts";
import { handleComboChat } from "@omniroute/open-sse/services/combo.ts";
import { resolveBareModelToConnectionDefault } from "@omniroute/open-sse/services/model.ts";
import { findEmbeddingComboDimensionConflict } from "./familyGuard";
import {
formatMissingEmbeddingCredentialsError,
formatUnknownEmbeddingProviderError,
} from "./errors";
import { isPrivateHost, isCloudMetadataHost } from "@/shared/network/outboundUrlGuard";
import { calculateCost } from "@/lib/usage/costCalculator";
import { attachOmniRouteMetaHeaders } from "@/domain/omnirouteResponseMeta";
@@ -217,7 +221,7 @@ export async function createEmbeddingResponse(
if (!providerConfig) {
return errorResponse(
HTTP_STATUS.BAD_REQUEST,
`Unknown embedding provider: ${provider}. No matching hardcoded or local provider found.`
formatUnknownEmbeddingProviderError(provider, resolvedModel)
);
}
@@ -227,7 +231,7 @@ export async function createEmbeddingResponse(
if (!credentials) {
return errorResponse(
HTTP_STATUS.BAD_REQUEST,
`No credentials for embedding provider: ${provider}`
formatMissingEmbeddingCredentialsError(provider)
);
}
if ("allRateLimited" in credentials && credentials.allRateLimited) {

View File

@@ -0,0 +1,38 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
formatMissingEmbeddingCredentialsError,
formatUnknownEmbeddingProviderError,
OPENROUTER_GEMINI_EMBEDDING_2,
OPENROUTER_GEMINI_EMBEDDING_2_PREVIEW,
NATIVE_GEMINI_EMBEDDING_2,
} from "../../src/lib/embeddings/errors.ts";
test("missing gemini embed credentials name the working OpenRouter ids", () => {
const message = formatMissingEmbeddingCredentialsError("gemini");
assert.match(message, /^No credentials for embedding provider: gemini/);
assert.match(message, new RegExp(OPENROUTER_GEMINI_EMBEDDING_2));
assert.match(message, new RegExp(OPENROUTER_GEMINI_EMBEDDING_2_PREVIEW));
assert.match(message, /Google AI Studio/);
});
test("missing credentials for other providers stay a short 400", () => {
assert.equal(
formatMissingEmbeddingCredentialsError("openai"),
"No credentials for embedding provider: openai"
);
});
test("unknown google provider names Gemini Embedding 2 workarounds", () => {
const message = formatUnknownEmbeddingProviderError("google", "gemini-embedding-2");
assert.match(message, /^Unknown embedding provider: google/);
assert.match(message, new RegExp(OPENROUTER_GEMINI_EMBEDDING_2));
assert.match(message, new RegExp(NATIVE_GEMINI_EMBEDDING_2));
});
test("unknown unrelated providers stay a short 400", () => {
assert.equal(
formatUnknownEmbeddingProviderError("not-a-provider", "text-embedding-3-small"),
"Unknown embedding provider: not-a-provider. No matching hardcoded or local provider found."
);
});