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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(perplexity-web):** an unmapped model name now returns a local `404 model_not_found` before any upstream call, instead of being forwarded to Perplexity as a raw `model_preference` ([#17822](https://github.com/diegosouzapw/OmniRoute/pull/17822)) — thanks @HouMinXi
19 changes: 15 additions & 4 deletions open-sse/executors/perplexity-web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import {
import { prepareToolMessages } from "../translator/webTools.ts";
import { currentAppliedProxySink } from "../utils/proxyFetch.ts";
import { buildToolModeResponse } from "./chatgptWebTools.ts";
import { projectPublicErrorIdentifier, sanitizeErrorMessage } from "../utils/error.ts";
import {
buildErrorBody,
projectPublicErrorIdentifier,
sanitizeErrorMessage,
} from "../utils/error.ts";
import { buildSessionCookieHeader, mergeRefreshedCookie } from "../utils/nextAuthCookie.ts";
import { formatTranslatedStreamError } from "../utils/streamErrorFormat.ts";
import {
Expand Down Expand Up @@ -491,9 +495,16 @@ export class PerplexityWebExecutor extends BaseExecutor {
} else if (MODEL_MAP[model]) {
[pplxMode, modelPref] = MODEL_MAP[model];
} else {
pplxMode = "copilot";
modelPref = model;
log?.info?.("PPLX-WEB", `Unmapped model ${model}, using as raw preference`);
const errResp = new Response(
JSON.stringify(
buildErrorBody(404, `Unknown model: ${model}`, undefined, {
type: "invalid_request_error",
code: "model_not_found",
})
),
{ status: 404, headers: { "Content-Type": "application/json" } }
);
return { response: errResp, url: PPLX_SSE_ENDPOINT, headers: {}, transformedBody: body };
}

// Parse messages and check session continuity
Expand Down
69 changes: 69 additions & 0 deletions tests/unit/perplexity-web-unmapped-model.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// @ts-nocheck
import test from "node:test";
import assert from "node:assert/strict";

const { PerplexityWebExecutor } = await import("../../open-sse/executors/perplexity-web.ts");
const { __setTlsFetchOverrideForTesting } =
await import("../../open-sse/services/perplexityTlsClient.ts");

__setTlsFetchOverrideForTesting(async (url, opts) => {
const res = await globalThis.fetch(url, opts);
return {
status: res.status,
headers: res.headers,
text: res.status === 200 ? null : await res.text(),
body: res.status === 200 ? res.body : null,
};
});

function okStream() {
const body = 'event: message\r\ndata: {"status":"COMPLETED","blocks":[]}\r\n\r\n';
return new Response(body, { status: 200, headers: { "Content-Type": "text/event-stream" } });
}

async function run(model) {
let fetches = 0;
const original = globalThis.fetch;
globalThis.fetch = async () => {
fetches += 1;
return okStream();
};
try {
const executor = new PerplexityWebExecutor();
const out = await executor.execute({
model,
body: { messages: [{ role: "user", content: "hello" }], stream: false },
stream: false,
credentials: { apiKey: "test-cookie" },
signal: AbortSignal.timeout(10000),
log: null,
});
const res = out.response;
return { status: res.status, body: await res.json(), fetches };
} finally {
globalThis.fetch = original;
}
}

test("an unmapped model name is rejected locally and never reaches perplexity", async () => {
const { status, body, fetches } = await run("pplx-deep-research");
assert.equal(status, 404);
assert.equal(body.error.code, "model_not_found");
assert.equal(fetches, 0);
});

test("a mapped model still reaches perplexity, so a counter stuck at zero fails", async () => {
const { fetches } = await run("pplx-sonar");
assert.equal(fetches, 1);
});

test("the unmapped-model 404 goes through the shared error body and never echoes a credential", async () => {
const secret = "sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789";
const { status, body, fetches } = await run(`pplx-${secret}`);
assert.equal(status, 404);
assert.equal(body.error.type, "invalid_request_error");
assert.equal(body.error.code, "model_not_found");
assert.match(body.error.message, /^Unknown model: /);
assert.ok(!body.error.message.includes(secret), "credential-shaped model name must be redacted");
assert.equal(fetches, 0);
});
Loading