Compare commits

...

1 Commits

Author SHA1 Message Date
Markus Hartung
65ab30ca98 fix(db): include local no-API-key providers in Qdrant embedding-model list (#11949)
The embedding-models route only treated a provider connection as "configured"
when it had a real apiKey or authType "oauth". Local/self-hosted providers
(ollama-local, lm-studio, vllm, etc.) connect through the connections route
with authType "apikey" but no apiKey — the connect form never asks for one
(src/shared/constants/providers/local.ts) — so an active local connection's
embedding models (embeddinggemma, nomic-embed-text, bge-m3 for ollama-local)
never surfaced in the dropdown even though the connection tested "Ativo".

Reuse the existing canonical providerAllowsOptionalApiKey() helper
(src/shared/constants/providers.ts) — already used for the identical
requiresApiKey check in src/app/api/providers/[id]/test/route.ts and across
the Zod validation schemas — instead of hardcoding a provider id list.
2026-08-29 05:45:02 -03:00
3 changed files with 68 additions and 1 deletions

View File

@@ -0,0 +1 @@
- **fix(db):** the Qdrant embedding-model dropdown now lists local/self-hosted providers (Ollama, LM Studio, vLLM, etc.) — an active connection is treated as "configured" when the provider allows an optional API key, not only when it has a real key or OAuth, so a running local embedding provider is no longer hidden from the picker ([#11949](https://github.com/diegosouzapw/OmniRoute/issues/11949))

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { getProviderConnections } from "@/lib/db/providers";
import { providerAllowsOptionalApiKey } from "@/shared/constants/providers";
import { getAllEmbeddingModels } from "@omniroute/open-sse/config/embeddingRegistry.ts";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
@@ -28,7 +29,12 @@ export async function GET(request: NextRequest) {
.filter(
(connection) =>
(typeof connection.apiKey === "string" && connection.apiKey.trim().length > 0) ||
connection.authType === "oauth"
connection.authType === "oauth" ||
// Local/self-hosted providers (ollama-local, lm-studio, etc.) and other
// no-key-required providers connect with no apiKey and authType "apikey"
// (see src/app/api/providers/route.ts) — they are still "configured" the
// moment the connection is active. See issue #11949.
providerAllowsOptionalApiKey(connection.provider)
)
.map((connection) => String(connection.provider || ""))
.filter(Boolean)

View File

@@ -43,6 +43,13 @@ const qdrantEmbeddingModelsRoute =
// returns the Fetch API Request, which is structurally sufficient at runtime.
const asNextRequest = (req: Request) => req as unknown as import("next/server").NextRequest;
// Mirrors EmbeddingModelOption from the embedding-models route response shape.
interface EmbeddingModelOptionLike {
value: string;
label: string;
dimensions?: number;
}
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
@@ -409,6 +416,59 @@ test("GET /api/settings/qdrant/embedding-models — lists only configured provid
assert.match(defaultModel.label, /1536d/);
});
test("GET /api/settings/qdrant/embedding-models — includes an active local no-API-key provider (#11949)", async () => {
// Local providers (e.g. ollama-local) are created through the connections
// route with authType "apikey" but no apiKey — the connect form never asks
// for one (src/shared/constants/providers/local.ts). An active connection
// like this must still surface its embedding models.
await localDb.createProviderConnection({
provider: "ollama-local",
authType: "apikey",
name: "local-ollama",
});
const headers = await createManagementSessionHeaders();
const req = new Request("http://localhost/api/settings/qdrant/embedding-models", {
method: "GET",
headers: Object.fromEntries(headers.entries()),
});
const res = await qdrantEmbeddingModelsRoute.GET(asNextRequest(req));
assert.strictEqual(res.status, 200);
const body = await res.json();
assert.ok(
body.models.some(
(model: EmbeddingModelOptionLike) => model.value === "ollama-local/embeddinggemma"
),
"should list ollama-local embedding models for an active, key-less local connection"
);
});
test("GET /api/settings/qdrant/embedding-models — still excludes a remote provider with no key", async () => {
// A remote provider that DOES require a key must stay excluded when the
// connection was created/left without one — regression guard for the fix
// above so it does not become "drop the filter entirely".
await localDb.createProviderConnection({
provider: "openai",
authType: "apikey",
name: "embedding-test-openai-no-key",
});
const headers = await createManagementSessionHeaders();
const req = new Request("http://localhost/api/settings/qdrant/embedding-models", {
method: "GET",
headers: Object.fromEntries(headers.entries()),
});
const res = await qdrantEmbeddingModelsRoute.GET(asNextRequest(req));
assert.strictEqual(res.status, 200);
const body = await res.json();
assert.ok(
body.models.every((model: EmbeddingModelOptionLike) => !model.value.startsWith("openai/")),
"should not list a remote provider's models when its active connection has no API key"
);
});
test("GET /api/settings/qdrant/embedding-models — 401 without auth", async () => {
await setRequireLogin(true);
const req = makeUnauthRequest("GET", "http://localhost/api/settings/qdrant/embedding-models");