From caeee8970de2114c1724996616879dcc1d6c487f Mon Sep 17 00:00:00 2001 From: backryun Date: Sat, 4 Jul 2026 04:50:58 +0900 Subject: [PATCH] fix(providers): update Perplexity Web models (#6106) Refresh the Perplexity Web model catalog + mode/model_preference mappings to the current live set. Regression guard: perplexity-web.test.ts. Co-authored-by: diegosouzapw --- CHANGELOG.md | 2 + .../registry/perplexity/web/index.ts | 12 ++-- open-sse/executors/perplexity-web.ts | 2 +- open-sse/executors/perplexity-web/protocol.ts | 25 +++++--- tests/unit/perplexity-web.test.ts | 64 +++++++++++++++---- 5 files changed, 77 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15e699bbe1..074e276841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ ### 🔧 Bug Fixes +- **providers (Perplexity Web):** refresh the Perplexity Web model catalog to the current set (GPT-5.4/5.5, Claude Sonnet 5.0 / Opus 4.8, GLM-5.2, Kimi K2.6, Nemotron 3 Ultra) and update the internal mode / `model_preference` mappings and thinking variants so requests resolve to live upstream models. Regression guard: `tests/unit/perplexity-web.test.ts`. ([#6106](https://github.com/diegosouzapw/OmniRoute/pull/6106) — thanks @backryun) + - **dashboard ("Update now" → Internal Server Error):** clicking **Update now** on the dashboard home could crash the page with a blank "Internal Server Error" screen (`Minified React error #31`). The handler POSTs the loopback-only `/api/system/version` auto-update endpoint and, on a non-OK JSON response (e.g. a `403` when the dashboard is reached through a reverse proxy / non-loopback origin), passed the raw error envelope object `{ error: { code, message, correlation_id } }` straight to `notify.error()`, which rendered the object as a React child and threw #31. The update-error path now funnels the body through `extractApiErrorMessage()` (the same safe extractor added in #5340), so a readable string always reaches the toast. Regression guard: `tests/unit/ui/home-update-error-render-5991.test.ts`. ([#5991](https://github.com/diegosouzapw/OmniRoute/issues/5991)) ### 📝 Maintenance diff --git a/open-sse/config/providers/registry/perplexity/web/index.ts b/open-sse/config/providers/registry/perplexity/web/index.ts index 67b7bbeb8c..36715483f9 100644 --- a/open-sse/config/providers/registry/perplexity/web/index.ts +++ b/open-sse/config/providers/registry/perplexity/web/index.ts @@ -9,13 +9,15 @@ export const perplexity_webProvider: RegistryEntry = { authType: "apikey", authHeader: "cookie", models: [ - { id: "pplx-auto", name: "Perplexity Auto (Free)" }, - { id: "pplx-sonar", name: "Perplexity Sonar" }, + { id: "pplx-auto", name: "Perplexity Best" }, + { id: "pplx-sonar", name: "Sonar 2 (via Perplexity)" }, + { id: "pplx-gpt-5.4", name: "GPT-5.4 (via Perplexity)" }, { id: "pplx-gpt", name: "GPT-5.5 (via Perplexity)" }, { id: "pplx-gemini", name: "Gemini 3.1 Pro (via Perplexity)" }, - { id: "pplx-sonnet", name: "Claude Sonnet 4.6 (via Perplexity)" }, - { id: "pplx-opus", name: "Claude Opus 4.7 (via Perplexity)" }, + { id: "pplx-sonnet", name: "Claude Sonnet 5.0 (via Perplexity)" }, + { id: "pplx-opus", name: "Claude Opus 4.8 (via Perplexity)" }, + { id: "pplx-glm", name: "GLM-5.2 (via Perplexity)" }, { id: "pplx-kimi", name: "Kimi K2.6 (via Perplexity)" }, - { id: "pplx-nemotron", name: "Nemotron 3 Super (via Perplexity)" }, + { id: "pplx-nemotron", name: "Nemotron 3 Ultra (via Perplexity)" }, ], }; diff --git a/open-sse/executors/perplexity-web.ts b/open-sse/executors/perplexity-web.ts index d5c693cff3..21e9148882 100644 --- a/open-sse/executors/perplexity-web.ts +++ b/open-sse/executors/perplexity-web.ts @@ -346,7 +346,7 @@ export class PerplexityWebExecutor extends BaseExecutor { let pplxMode: string; let modelPref: string; if (thinking && THINKING_MAP[model]) { - pplxMode = "copilot"; + pplxMode = "search"; modelPref = THINKING_MAP[model]; log?.info?.("PPLX-WEB", `Thinking mode → ${model} using ${modelPref}`); } else if (MODEL_MAP[model]) { diff --git a/open-sse/executors/perplexity-web/protocol.ts b/open-sse/executors/perplexity-web/protocol.ts index 102ef3f9b9..25b5aa41fb 100644 --- a/open-sse/executors/perplexity-web/protocol.ts +++ b/open-sse/executors/perplexity-web/protocol.ts @@ -46,19 +46,24 @@ export const PPLX_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0"; export const MODEL_MAP: Record = { - "pplx-auto": ["concise", "pplx_pro"], - "pplx-sonar": ["copilot", "experimental"], - "pplx-gpt": ["copilot", "gpt54"], - "pplx-gemini": ["copilot", "gemini31pro_high"], - "pplx-sonnet": ["copilot", "claude46sonnet"], - "pplx-opus": ["copilot", "claude46opus"], - "pplx-nemotron": ["copilot", "nv_nemotron_3_super"], + "pplx-auto": ["search", "pplx_pro"], + "pplx-sonar": ["search", "experimental"], + "pplx-gpt-5.4": ["search", "gpt54"], + "pplx-gpt": ["search", "gpt55"], + "pplx-gemini": ["search", "gemini31pro_high"], + "pplx-sonnet": ["search", "claude50sonnet"], + "pplx-opus": ["search", "claude48opus"], + "pplx-glm": ["search", "glm_5_2"], + "pplx-kimi": ["search", "kimik26instant"], + "pplx-nemotron": ["search", "nv_nemotron_3_ultra"], }; export const THINKING_MAP: Record = { - "pplx-gpt": "gpt54_thinking", - "pplx-sonnet": "claude46sonnetthinking", - "pplx-opus": "claude46opusthinking", + "pplx-gpt-5.4": "gpt54_thinking", + "pplx-gpt": "gpt55_thinking", + "pplx-sonnet": "claude50sonnetthinking", + "pplx-opus": "claude48opusthinking", + "pplx-kimi": "kimik26thinking", }; export const CITATION_RE = /\[\d+\]/g; diff --git a/tests/unit/perplexity-web.test.ts b/tests/unit/perplexity-web.test.ts index a8c2b45c06..5b73a0a746 100644 --- a/tests/unit/perplexity-web.test.ts +++ b/tests/unit/perplexity-web.test.ts @@ -271,7 +271,10 @@ test("Schematized API: diff_block chunks reconstruct answer (non-streaming)", as blocks: [ { intended_usage: "ask_text_0_markdown", - diff_block: { field: "markdown_block", patches: [{ op: "add", path: "/chunks/1", value: "answer " }] }, + diff_block: { + field: "markdown_block", + patches: [{ op: "add", path: "/chunks/1", value: "answer " }], + }, }, ], }, @@ -281,7 +284,10 @@ test("Schematized API: diff_block chunks reconstruct answer (non-streaming)", as blocks: [ { intended_usage: "ask_text_0_markdown", - diff_block: { field: "markdown_block", patches: [{ op: "add", path: "/chunks/2", value: "is 42." }] }, + diff_block: { + field: "markdown_block", + patches: [{ op: "add", path: "/chunks/2", value: "is 42." }], + }, }, ], }, @@ -291,7 +297,11 @@ test("Schematized API: diff_block chunks reconstruct answer (non-streaming)", as blocks: [ { intended_usage: "ask_text_0_markdown", - markdown_block: { progress: "DONE", chunks: ["The answer is 42."], answer: "The answer is 42." }, + markdown_block: { + progress: "DONE", + chunks: ["The answer is 42."], + answer: "The answer is 42.", + }, }, ], }, @@ -325,7 +335,12 @@ test("Schematized API: diff_block streams incremental deltas", async () => { blocks: [ { intended_usage: "ask_text_0_markdown", - diff_block: { field: "markdown_block", patches: [{ op: "replace", path: "", value: { progress: "IN_PROGRESS", chunks: ["one, "] } }] }, + diff_block: { + field: "markdown_block", + patches: [ + { op: "replace", path: "", value: { progress: "IN_PROGRESS", chunks: ["one, "] } }, + ], + }, }, ], }, @@ -334,7 +349,10 @@ test("Schematized API: diff_block streams incremental deltas", async () => { blocks: [ { intended_usage: "ask_text_0_markdown", - diff_block: { field: "markdown_block", patches: [{ op: "add", path: "/chunks/1", value: "two, " }] }, + diff_block: { + field: "markdown_block", + patches: [{ op: "add", path: "/chunks/1", value: "two, " }], + }, }, ], }, @@ -344,7 +362,11 @@ test("Schematized API: diff_block streams incremental deltas", async () => { blocks: [ { intended_usage: "ask_text_0_markdown", - markdown_block: { progress: "DONE", chunks: ["one, two, three"], answer: "one, two, three" }, + markdown_block: { + progress: "DONE", + chunks: ["one, two, three"], + answer: "one, two, three", + }, }, ], }, @@ -743,7 +765,7 @@ test("Auth: JWT auth sends Authorization Bearer header", async () => { // ─── Test: Model mapping ──────────────────────────────────────────────────── -test("Model mapping: pplx-gpt sends correct internal preference", async () => { +test("Model mapping: pplx-gpt sends current GPT-5.5 internal preference", async () => { let capturedBody = null; const original = globalThis.fetch; globalThis.fetch = async (url, opts) => { @@ -775,8 +797,8 @@ test("Model mapping: pplx-gpt sends correct internal preference", async () => { log: null, }); - assert.equal(capturedBody.params.model_preference, "gpt54"); - assert.equal(capturedBody.params.mode, "copilot"); + assert.equal(capturedBody.params.model_preference, "gpt55"); + assert.equal(capturedBody.params.mode, "search"); } finally { globalThis.fetch = original; } @@ -818,7 +840,8 @@ test("Model mapping: thinking mode uses thinking variant", async () => { log: null, }); - assert.equal(capturedBody.params.model_preference, "claude46sonnetthinking"); + assert.equal(capturedBody.params.model_preference, "claude50sonnetthinking"); + assert.equal(capturedBody.params.mode, "search"); } finally { globalThis.fetch = original; } @@ -831,17 +854,31 @@ test("Provider registry: perplexity-web is registered with correct models", asyn const models = PROVIDER_MODELS["pplx-web"]; assert.ok(models, "pplx-web should be in PROVIDER_MODELS"); - assert.ok(models.length === 8, `Expected 8 models, got ${models.length}`); + assert.ok(models.length === 10, `Expected 10 models, got ${models.length}`); const modelIds = models.map((m) => m.id); assert.ok(modelIds.includes("pplx-auto")); assert.ok(modelIds.includes("pplx-gpt")); + assert.ok(modelIds.includes("pplx-gpt-5.4")); assert.ok(modelIds.includes("pplx-sonnet")); assert.ok(modelIds.includes("pplx-opus")); assert.ok(modelIds.includes("pplx-gemini")); assert.ok(modelIds.includes("pplx-nemotron")); assert.ok(modelIds.includes("pplx-sonar")); assert.ok(modelIds.includes("pplx-kimi")); + assert.ok(modelIds.includes("pplx-glm")); +}); + +test("Provider registry: every advertised perplexity-web model has an explicit internal mapping", async () => { + const { PROVIDER_MODELS } = await import("../../open-sse/config/providerModels.ts"); + const { MODEL_MAP } = await import("../../open-sse/executors/perplexity-web/protocol.ts"); + + const missing = PROVIDER_MODELS["pplx-web"].filter((model) => !MODEL_MAP[model.id]); + assert.deepEqual( + missing.map((model) => model.id), + [], + "all advertised Perplexity Web models should map to an explicit model_preference" + ); }); // ─── Test: Fallback text field ────────────────────────────────────────────── @@ -903,7 +940,10 @@ test("Request: posts to correct Perplexity SSE endpoint", async () => { assert.equal(capturedUrl, "https://www.perplexity.ai/rest/sse/perplexity_ask"); assert.equal(capturedHeaders["Origin"], "https://www.perplexity.ai"); - assert.equal(capturedHeaders["x-perplexity-request-endpoint"], "https://www.perplexity.ai/rest/sse/perplexity_ask"); + assert.equal( + capturedHeaders["x-perplexity-request-endpoint"], + "https://www.perplexity.ai/rest/sse/perplexity_ask" + ); assert.equal(capturedHeaders["x-perplexity-request-reason"], "ask-query-state-provider"); assert.ok(capturedHeaders["x-request-id"], "x-request-id header should be set"); assert.equal(capturedHeaders["Accept"], "text/event-stream");