From 3e03daaa7aa85f243d3dec889a40ac4ee85df83c Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Mon, 22 Jun 2026 12:28:41 -0300 Subject: [PATCH] fix(perplexity): validate API keys via /v1/models endpoint Perplexity deprecated the unversioned `/models` endpoint (now 404), so the OpenAI-like key validator (validateOpenAILikeProvider in src/lib/providers/validation.ts) would derive `/models` via addModelsSuffix from the perplexity registry baseUrl and hit a dead endpoint, misclassifying valid keys. Pin an explicit `modelsUrl` on the perplexity registry entry so validation probes `/v1/models` directly. The dashboard models discovery route (src/app/api/providers/[id]/models/route.ts) already tries `/v1/models` first via its endpoints fallback list, so it is unaffected. Co-authored-by: gaoliao1688 Inspired-by: https://github.com/decolua/9router/commit/db4499d6 --- .../providers/registry/perplexity/index.ts | 5 ++ .../perplexity-key-validation-models.test.ts | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/unit/perplexity-key-validation-models.test.ts diff --git a/open-sse/config/providers/registry/perplexity/index.ts b/open-sse/config/providers/registry/perplexity/index.ts index 97f1844ad9..14b90d413d 100644 --- a/open-sse/config/providers/registry/perplexity/index.ts +++ b/open-sse/config/providers/registry/perplexity/index.ts @@ -6,6 +6,11 @@ export const perplexityProvider: RegistryEntry = { format: "openai", executor: "default", baseUrl: "https://api.perplexity.ai/chat/completions", + // Perplexity deprecated the unversioned `/models` endpoint (returns 404), so + // pin an explicit `modelsUrl` here. Without it, validateOpenAILikeProvider + // (src/lib/providers/validation.ts) derives `/models` via + // addModelsSuffix and probes the dead endpoint, misclassifying valid keys. + modelsUrl: "https://api.perplexity.ai/v1/models", authType: "apikey", authHeader: "bearer", models: [ diff --git a/tests/unit/perplexity-key-validation-models.test.ts b/tests/unit/perplexity-key-validation-models.test.ts new file mode 100644 index 0000000000..ee078ea5cb --- /dev/null +++ b/tests/unit/perplexity-key-validation-models.test.ts @@ -0,0 +1,50 @@ +// Regression test for perplexity API key validation. +// +// Perplexity deprecated the unversioned `/models` endpoint (returns 404), so +// our default validation probe — which derives `/models` from the +// perplexity registry entry via `addModelsSuffix` — would always fail to +// confirm a valid key, falling through to the chat-completions probe and +// often misclassifying live keys as "Invalid". Inspired by upstream +// 9router fix (see commit message); we port it OmniRoute-style by +// declaring an explicit `modelsUrl` on the perplexity registry entry. + +import { describe, it } from "node:test"; +import assert from "node:assert"; + +describe("perplexity registry — key validation models endpoint", () => { + it("declares a modelsUrl pointing at /v1/models (not the deprecated /models)", async () => { + const { getRegistryEntry } = await import( + "../../open-sse/config/providerRegistry.ts" + ); + const entry = getRegistryEntry("perplexity"); + assert.ok(entry, "perplexity must be registered in the execution registry"); + assert.equal(entry.format, "openai"); + assert.ok( + typeof entry.modelsUrl === "string" && entry.modelsUrl.length > 0, + "perplexity registry entry must declare an explicit modelsUrl so key " + + "validation does not hit the deprecated /models endpoint" + ); + assert.equal( + entry.modelsUrl, + "https://api.perplexity.ai/v1/models", + "perplexity modelsUrl must point at the versioned /v1/models endpoint " + + "(unversioned /models was deprecated and now returns 404)" + ); + }); + + it("does not derive a /models URL that ends in /chat/completions/models", async () => { + const { getRegistryEntry } = await import( + "../../open-sse/config/providerRegistry.ts" + ); + const entry = getRegistryEntry("perplexity"); + assert.ok(entry?.modelsUrl, "modelsUrl required"); + assert.ok( + !entry.modelsUrl.includes("/chat/completions"), + "modelsUrl must not include /chat/completions" + ); + assert.ok( + entry.modelsUrl.endsWith("/v1/models"), + "modelsUrl must end with /v1/models" + ); + }); +});