Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions open-sse/config/providers/registry/perplexity/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)" },
],
};
2 changes: 1 addition & 1 deletion open-sse/executors/perplexity-web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]) {
Expand Down
25 changes: 15 additions & 10 deletions open-sse/executors/perplexity-web/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
64 changes: 52 additions & 12 deletions tests/unit/perplexity-web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 " }],
},
},
],
},
Expand All @@ -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." }],
},
},
],
},
Expand All @@ -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.",
},
},
],
},
Expand Down Expand Up @@ -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, "] } },
],
},
},
],
},
Expand All @@ -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, " }],
},
},
],
},
Expand All @@ -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",
},
},
],
},
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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 ──────────────────────────────────────────────
Expand Down Expand Up @@ -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");
Expand Down
Loading