mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
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:
@@ -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
|
||||
|
||||
@@ -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)" },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user