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 <diegosouza.pw@gmail.com>
This commit is contained in:
backryun
2026-07-04 04:50:58 +09:00
committed by GitHub
parent 1f1fda7cf5
commit caeee8970d
5 changed files with 77 additions and 28 deletions

View File

@@ -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

View File

@@ -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)" },
],
};

View File

@@ -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]) {

View File

@@ -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<string, [string, string]> = {
"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<string, string> = {
"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;

View File

@@ -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");