mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-23 07:32:20 +03:00
fix(providers): perplexity import filters to Sonar-family models (#11060)
This commit is contained in:
1
changelog.d/fixes/11060-perplexity-filter.md
Normal file
1
changelog.d/fixes/11060-perplexity-filter.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(providers): filter Perplexity model import to the Sonar family so Agent-API catalog ids stop surfacing as routable chat models (#11060)
|
||||
@@ -87,6 +87,22 @@ export function parseAlibabaModelStudioModelsForConnection(
|
||||
export function parseQwenCloudTextModels(data: any): any[] {
|
||||
return parseCuratedDashscopeModels(data, QWEN_CLOUD_TEXT_MODELS, QWEN_CLOUD_TEXT_MODEL_IDS);
|
||||
}
|
||||
|
||||
// Perplexity's /v1/models lists the Agent API catalog (vendor-prefixed ids like
|
||||
// "anthropic/claude-fable-5"), but chat requests always go to the classic
|
||||
// /chat/completions endpoint, which only accepts the Sonar family. Filter
|
||||
// discovery to Sonar-family ids so agent-style ids never surface as routable
|
||||
// chat models (#11060). Bounded pattern — no ReDoS-prone quantifiers.
|
||||
export function parsePerplexitySonarModels(data: any): any[] {
|
||||
const models = Array.isArray(data?.data)
|
||||
? data.data
|
||||
: Array.isArray(data?.models)
|
||||
? data.models
|
||||
: [];
|
||||
return models.filter(
|
||||
(model: any) => typeof model?.id === "string" && /^sonar(-|$)/.test(model.id)
|
||||
);
|
||||
}
|
||||
type ProviderModelsHeaderContext = {
|
||||
authType?: string;
|
||||
providerSpecificData?: unknown;
|
||||
@@ -659,6 +675,17 @@ export const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> =
|
||||
headers: { Accept: "application/json" },
|
||||
parseResponse: parseClinepassRecommendedModels,
|
||||
},
|
||||
// Perplexity's /v1/models lists the Agent API catalog (vendor-prefixed agent
|
||||
// ids), but chat only accepts the Sonar family on /chat/completions. Import
|
||||
// must keep Sonar-family ids only (#11060).
|
||||
perplexity: {
|
||||
url: "https://api.perplexity.ai/v1/models",
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer ",
|
||||
parseResponse: parsePerplexitySonarModels,
|
||||
},
|
||||
cohere: {
|
||||
url: "https://api.cohere.com/v2/models",
|
||||
method: "GET",
|
||||
|
||||
63
tests/unit/perplexity-discovery-filter.test.ts
Normal file
63
tests/unit/perplexity-discovery-filter.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { PROVIDER_MODELS_CONFIG } from "../../src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts";
|
||||
|
||||
// Regression guard for #11060 — Perplexity's /v1/models endpoint lists the
|
||||
// Agent API catalog (vendor-prefixed ids like "anthropic/claude-fable-5"), but
|
||||
// chat requests always go to the classic /chat/completions endpoint, which only
|
||||
// accepts the Sonar family. Without a PROVIDER_MODELS_CONFIG entry, generic
|
||||
// model import pulled those agent-style ids into the connection's chat model
|
||||
// list and every routed request failed with 400 "Invalid model". The discovery
|
||||
// entry must exist and its parseResponse must keep only Sonar-family ids.
|
||||
|
||||
test("perplexity has a discovery entry in PROVIDER_MODELS_CONFIG", () => {
|
||||
const cfg = PROVIDER_MODELS_CONFIG.perplexity;
|
||||
assert.ok(cfg, "expected a perplexity entry in PROVIDER_MODELS_CONFIG");
|
||||
assert.equal(cfg.method, "GET");
|
||||
assert.equal(cfg.url, "https://api.perplexity.ai/v1/models");
|
||||
assert.equal(typeof cfg.parseResponse, "function");
|
||||
});
|
||||
|
||||
test("perplexity parseResponse keeps only the Sonar family (#11060)", () => {
|
||||
const cfg = PROVIDER_MODELS_CONFIG.perplexity;
|
||||
const models = cfg.parseResponse({
|
||||
object: "list",
|
||||
data: [
|
||||
{ id: "anthropic/claude-fable-5", object: "model", owned_by: "anthropic" },
|
||||
{ id: "sonar-pro", object: "model", owned_by: "perplexity" },
|
||||
{ id: "sonar", object: "model", owned_by: "perplexity" },
|
||||
],
|
||||
}) as Array<{ id: string }>;
|
||||
|
||||
assert.deepEqual(
|
||||
models.map((model) => model.id),
|
||||
["sonar-pro", "sonar"]
|
||||
);
|
||||
});
|
||||
|
||||
test("perplexity parseResponse keeps every Sonar variant and drops non-Sonar ids", () => {
|
||||
const cfg = PROVIDER_MODELS_CONFIG.perplexity;
|
||||
const models = cfg.parseResponse({
|
||||
data: [
|
||||
{ id: "sonar-deep-research" },
|
||||
{ id: "sonar-reasoning-pro" },
|
||||
{ id: "sonar-pro" },
|
||||
{ id: "sonar" },
|
||||
{ id: "openai/gpt-5" },
|
||||
{ id: "sonarish" },
|
||||
],
|
||||
}) as Array<{ id: string }>;
|
||||
|
||||
assert.deepEqual(
|
||||
models.map((model) => model.id),
|
||||
["sonar-deep-research", "sonar-reasoning-pro", "sonar-pro", "sonar"]
|
||||
);
|
||||
});
|
||||
|
||||
test("perplexity parseResponse tolerates empty and malformed payloads", () => {
|
||||
const cfg = PROVIDER_MODELS_CONFIG.perplexity;
|
||||
assert.deepEqual(cfg.parseResponse({ data: [] }), []);
|
||||
assert.deepEqual(cfg.parseResponse(undefined), []);
|
||||
assert.deepEqual(cfg.parseResponse({}), []);
|
||||
});
|
||||
Reference in New Issue
Block a user