diff --git a/src/lib/providers/nvidiaValidationModel.ts b/src/lib/providers/nvidiaValidationModel.ts new file mode 100644 index 0000000000..bde123944b --- /dev/null +++ b/src/lib/providers/nvidiaValidationModel.ts @@ -0,0 +1,24 @@ +/** + * NVIDIA NIM key-validation probe model (#3116). + * + * Key validation does a tiny chat/completions probe and only cares whether auth passes + * (401/403 ⇒ bad key; anything else ⇒ key OK). The probe model therefore must be one + * that responds quickly for every account. The previous default was the first model in + * the catalog (`z-ai/glm-5.1`), which requires the "Public API Endpoints" account + * permission and has had DEGRADED windows — accounts lacking that permission see the + * probe HANG until the validation timeout, which surfaces as a misleading "Upstream + * Error" on an otherwise-valid key. + * + * `meta/llama-3.1-8b-instruct` is a long-lived, universally-available NIM model (no + * special permission), so it is a far more reliable auth probe. A connection may still + * override it via `providerSpecificData.validationModelId`. + */ +export const NVIDIA_DEFAULT_VALIDATION_MODEL = "meta/llama-3.1-8b-instruct"; + +export function resolveNvidiaValidationModel(providerSpecificData?: { + validationModelId?: unknown; +}): string { + const override = providerSpecificData?.validationModelId; + if (typeof override === "string" && override.trim()) return override.trim(); + return NVIDIA_DEFAULT_VALIDATION_MODEL; +} diff --git a/src/lib/providers/validation.ts b/src/lib/providers/validation.ts index 96e7845464..ec68ddeb30 100644 --- a/src/lib/providers/validation.ts +++ b/src/lib/providers/validation.ts @@ -34,6 +34,7 @@ import { normalizeSessionCookieHeader, } from "@/lib/providers/webCookieAuth"; import { buildJulesApiUrl } from "@/lib/cloudAgent/julesApi.ts"; +import { resolveNvidiaValidationModel } from "@/lib/providers/nvidiaValidationModel"; import { getGigachatAccessToken } from "@omniroute/open-sse/services/gigachatAuth.ts"; import { validateQoderCliPat } from "@omniroute/open-sse/services/qoderCli.ts"; import { @@ -3961,10 +3962,10 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi const chatUrl = normalized.endsWith("/chat/completions") ? normalized : `${normalized}/chat/completions`; - const modelId = - providerSpecificData?.validationModelId || - getRegistryEntry("nvidia")?.models?.[0]?.id || - "meta/llama-3.1-8b-instruct"; + // #3116: probe a universally-available model rather than models[0] + // (z-ai/glm-5.1), which requires the "Public API Endpoints" account permission + // and can hang/be DEGRADED — making a *valid* key fail with "Upstream Error". + const modelId = resolveNvidiaValidationModel(providerSpecificData); const res = await validationWrite( chatUrl, { diff --git a/tests/unit/nvidia-validation-model-3116.test.ts b/tests/unit/nvidia-validation-model-3116.test.ts new file mode 100644 index 0000000000..1d52f68059 --- /dev/null +++ b/tests/unit/nvidia-validation-model-3116.test.ts @@ -0,0 +1,29 @@ +/** + * #3116 — NVIDIA key validation probed the first catalog model (`z-ai/glm-5.1`), which + * requires the "Public API Endpoints" account permission and can hang/be DEGRADED, + * making a *valid* key fail with a misleading "Upstream Error". The probe now defaults to + * the universally-available `meta/llama-3.1-8b-instruct`, with a per-connection override. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { + NVIDIA_DEFAULT_VALIDATION_MODEL, + resolveNvidiaValidationModel, +} from "../../src/lib/providers/nvidiaValidationModel.ts"; + +test("defaults to a stable, permission-free NVIDIA model (not the gated glm-5.1)", () => { + assert.equal(NVIDIA_DEFAULT_VALIDATION_MODEL, "meta/llama-3.1-8b-instruct"); + assert.equal(resolveNvidiaValidationModel(), "meta/llama-3.1-8b-instruct"); + assert.equal(resolveNvidiaValidationModel({}), "meta/llama-3.1-8b-instruct"); + assert.notEqual(resolveNvidiaValidationModel(undefined), "z-ai/glm-5.1"); +}); + +test("honors a per-connection validationModelId override", () => { + assert.equal( + resolveNvidiaValidationModel({ validationModelId: "nvidia/llama-3.3-nemotron-super-49b" }), + "nvidia/llama-3.3-nemotron-super-49b" + ); + // blank/whitespace override falls back to the default + assert.equal(resolveNvidiaValidationModel({ validationModelId: " " }), NVIDIA_DEFAULT_VALIDATION_MODEL); +});