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
1 change: 1 addition & 0 deletions changelog.d/features/perplexity-agent-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **feat(providers):** add a Perplexity Agent API provider (`perplexity-agent` / `pplx-agent`) for Perplexity `/v1/responses`, including the documented Anthropic, OpenAI, Google, xAI, DeepSeek, Z.AI, Moonshot/Kimi, NVIDIA, and Perplexity model IDs plus Anthropic-model `max_output_tokens` compatibility.
19 changes: 10 additions & 9 deletions docs/reference/PROVIDER_REFERENCE.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---
title: "Provider Reference"
version: 3.8.51
lastUpdated: 2026-08-28
lastUpdated: 2026-08-30
---

# Provider Reference

> **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand.
> Regenerate with: `npm run gen:provider-reference`
> **Last generated:** 2026-08-28
> **Last generated:** 2026-08-30

Total providers: **351**. See category breakdown below.
Total providers: **352**. See category breakdown below.

## Categories

Expand Down Expand Up @@ -116,7 +116,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `zai-web` | `zw` | Z.ai Web | Web cookie | [link](https://chat.z.ai) | Copy the "token" value from chat.z.ai → DevTools → Application → Local Storage. Do not copy cookies; OmniRoute handles the per-request CAPTCHA through its browser transport. | — |
| `zenmux-free` | `zmf` | ZenMux Free (Web) | Web cookie | [link](https://zenmux.ai) | Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days. | — |

## API Key Providers (paid / paid-with-free-credits) (235)
## API Key Providers (paid / paid-with-free-credits) (236)

| ID | Alias | Name | Tags | Website | Notes |
|----|-------|------|------|---------|-------|
Expand Down Expand Up @@ -193,11 +193,11 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `freemodel-dev` | `fmd` | FreeModel.dev | API key | [link](https://freemodel.dev) | $300 free credits on signup — no credit card required. Access GPT-5.4 and GPT-5.5 (OpenAI's latest flagship models) through an OpenAI-compatible API. |
| `freetheai` | `fta` | FreeTheAi | API key, aggregator | [link](https://freetheai.xyz) | Join the FreeTheAi Discord to get your free API key. |
| `friendliai` | `friendli` | FriendliAI | API key | [link](https://friendli.ai) | Free tier for serverless inference — no credit card required |
| `g4f-gemini` | `g4fgem` | g4f.space — Gemini | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html. |
| `g4f-groq` | `g4fgroq` | g4f.space — Groq | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html. |
| `g4f-nvidia` | `g4fnv` | g4f.space — NVIDIA | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html. |
| `g4f-ollama` | `g4foll` | g4f.space — Ollama | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html. |
| `g4f-pollinations` | `g4fpol` | g4f.space — Pollinations | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or create a member API key at g4f.dev/members.html. |
| `g4f-gemini` | `g4fgem` | g4f.space — Gemini | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html). |
| `g4f-groq` | `g4fgroq` | g4f.space — Groq | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html). |
| `g4f-nvidia` | `g4fnv` | g4f.space — NVIDIA | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html). |
| `g4f-ollama` | `g4foll` | g4f.space — Ollama | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html). |
| `g4f-pollinations` | `g4fpol` | g4f.space — Pollinations | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html). |
| `galadriel` | `galadriel` | Galadriel | API key | [link](https://galadriel.com) | ⚠️ **DEPRECATED.** api.galadriel.ai no longer resolves (sweep 2026-06-19); the inference API appears discontinued. |
| `gemini` | `gemini` | Gemini (Google AI Studio) | API key | [link](https://aistudio.google.com) | Free tier available through Google AI Studio; current per-model quotas and regional limits apply |
| `getgoapi` | `ggo` | GoAPI | API key, aggregator | [link](https://api.getgoapi.com) | — |
Expand Down Expand Up @@ -283,6 +283,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `orcarouter` | `orcarouter` | OrcaRouter | API key | [link](https://www.orcarouter.ai) | — |
| `ovhcloud` | `ovh` | OVHcloud AI | API key | [link](https://www.ovhcloud.com) | — |
| `perplexity` | `pplx` | Perplexity | API key | [link](https://www.perplexity.ai) | — |
| `perplexity-agent` | `pplx-agent` | Perplexity Agent | API key | [link](https://www.perplexity.ai) | Use your Perplexity API key. OmniRoute routes Agent API model IDs through Perplexity's Responses-compatible endpoint. |
| `piapi` | `pi` | PiAPI | API key, aggregator | [link](https://piapi.ai) | — |
| `pioneer` | `pn` | Pioneer AI | API key | [link](https://pioneer.ai) | $75 free usage credits — no credit card required |
| `plamo` | `plamo` | PLaMo | API key | [link](https://plamo.preferredai.jp/api) | — |
Expand Down
2 changes: 2 additions & 0 deletions open-sse/config/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import { moonshotProvider } from "./registry/moonshot/index.ts";
import { poeProvider } from "./registry/poe/index.ts";
import { bazaarlinkProvider } from "./registry/bazaarlink/index.ts";
import { perplexityProvider } from "./registry/perplexity/index.ts";
import { perplexityAgentProvider } from "./registry/perplexity/agent/index.ts";
import { perplexity_webProvider } from "./registry/perplexity/web/index.ts";
import { minimaxProvider } from "./registry/minimax/index.ts";
import { minimax_cnProvider } from "./registry/minimax/cn/index.ts";
Expand Down Expand Up @@ -376,6 +377,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
poe: poeProvider,
bazaarlink: bazaarlinkProvider,
perplexity: perplexityProvider,
"perplexity-agent": perplexityAgentProvider,
"perplexity-web": perplexity_webProvider,
minimax: minimaxProvider,
"minimax-cn": minimax_cnProvider,
Expand Down
29 changes: 29 additions & 0 deletions open-sse/config/providers/registry/perplexity/agent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { RegistryEntry } from "../../../shared.ts";

export const perplexityAgentProvider: RegistryEntry = {
id: "perplexity-agent",
alias: "pplx-agent",
format: "openai-responses",
executor: "default",
baseUrl: "https://api.perplexity.ai/v1/responses",
modelsUrl: "https://api.perplexity.ai/v1/models",
testKeyModelsUrl: "https://api.perplexity.ai/v1/models",
authType: "apikey",
authHeader: "bearer",
passthroughModels: true,
liveCatalogAuthoritative: false,
models: [
{
id: "openai/gpt-5.6-sol",
name: "GPT-5.6 Sol (Perplexity Agent)",
supportsReasoning: true,
toolCalling: true,
},
{
id: "perplexity/kimi-k3",
name: "Kimi K3 (Perplexity Agent)",
supportsReasoning: true,
supportedThinkingEfforts: ["minimal", "low", "medium", "high", "xhigh", "max"],
},
],
};
27 changes: 25 additions & 2 deletions open-sse/executors/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ import { resolveAlibabaProviderBaseUrl } from "@/shared/constants/alibabaProvide
import { usesCcWireImage } from "../services/ccWireImageBuiltins.ts";

const NVIDIA_TOOL_CALL_ID_PATTERN = /^[A-Za-z0-9]{9}$/;
const PERPLEXITY_AGENT_DEFAULT_MAX_OUTPUT_TOKENS = 4096;

function defaultPerplexityAgentMaxOutputTokens<T>(body: T): T {
if (!body || typeof body !== "object" || Array.isArray(body)) return body;

const record = body as Record<string, unknown>;
if (
record.max_output_tokens !== undefined ||
record.max_completion_tokens !== undefined ||
record.max_tokens !== undefined
) {
return body;
}

return {
...record,
max_output_tokens: PERPLEXITY_AGENT_DEFAULT_MAX_OUTPUT_TOKENS,
} as T;
}

function normalizeNvidiaToolCallId(id: unknown): unknown {
if (id === null || id === undefined) return id;
Expand Down Expand Up @@ -198,6 +217,8 @@ export class DefaultExecutor extends BaseExecutor {
}
}
switch (this.provider) {
case "perplexity-agent":
return this.config.baseUrl;
case "openai": {
// #5842: responses-only models (o1-pro / gpt-5.x-pro) 404 on
// /v1/chat/completions ("only supported in v1/responses"). Route them to
Expand Down Expand Up @@ -639,8 +660,7 @@ export class DefaultExecutor extends BaseExecutor {

const record = body as Record<string, unknown>;
const rf = record.response_format as
| { type?: string; json_schema?: { schema?: unknown } }
| undefined;
{ type?: string; json_schema?: { schema?: unknown } } | undefined;
if (!rf) return body;

// openai-compatible-* providers accept json_object natively — only the
Expand Down Expand Up @@ -725,6 +745,9 @@ export class DefaultExecutor extends BaseExecutor {

withDefaults = this.applyJsonSchemaFallback(withDefaults);
withDefaults = this.defaultResponsesTextFormat(withDefaults);
if (this.provider === "perplexity-agent") {
withDefaults = defaultPerplexityAgentMaxOutputTokens(withDefaults);
}

if (this.provider === "nvidia") {
normalizeNvidiaToolCallIds(withDefaults);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,9 @@ export function openaiToOpenAIResponsesRequest(
// Translate max_tokens / max_completion_tokens → max_output_tokens for Responses API.
// The Responses API does not accept max_tokens or max_completion_tokens; it requires
// max_output_tokens. max_completion_tokens takes priority as the newer Chat Completions field.
if (root.max_completion_tokens !== undefined) {
if (root.max_output_tokens !== undefined) {
result.max_output_tokens = root.max_output_tokens;
} else if (root.max_completion_tokens !== undefined) {
result.max_output_tokens = root.max_completion_tokens;
} else if (root.max_tokens !== undefined) {
result.max_output_tokens = root.max_tokens;
Expand Down
15 changes: 15 additions & 0 deletions src/shared/constants/providers/apikey/frontier-labs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ export const APIKEY_PROVIDERS_FRONTIER = {
textIcon: "PP",
website: "https://www.perplexity.ai",
},
"perplexity-agent": {
id: "perplexity-agent",
alias: "pplx-agent",
name: "Perplexity Agent",
icon: "search",
color: "#20808D",
textIcon: "PA",
website: "https://www.perplexity.ai",
authHint:
"Use your Perplexity API key. OmniRoute routes Agent API model IDs through Perplexity's Responses-compatible endpoint.",
apiHint:
"Use Agent API model IDs with the pplx-agent/ prefix, for example pplx-agent/openai/gpt-5.6-sol or pplx-agent/anthropic/claude-opus-4-5.",
passthroughModels: true,
serviceKinds: ["llm"],
},
cohere: {
id: "cohere",
alias: "cohere",
Expand Down
27 changes: 25 additions & 2 deletions tests/snapshots/provider/translate-path.json
Original file line number Diff line number Diff line change
Expand Up @@ -4611,8 +4611,8 @@
}
},
"url": {
"nonStream": "https://api.orcarouter.ai/v1",
"stream": "https://api.orcarouter.ai/v1"
"nonStream": "https://api.orcarouter.ai/v1/chat/completions",
"stream": "https://api.orcarouter.ai/v1/chat/completions"
}
},
"ovhcloud": {
Expand Down Expand Up @@ -4661,6 +4661,29 @@
"stream": "https://api.perplexity.ai/chat/completions"
}
},
"perplexity-agent": {
"format": "openai-responses",
"headers": {
"apiKey": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"nonStream": {
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"oauth": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
}
},
"url": {
"nonStream": "https://api.perplexity.ai/v1/responses",
"stream": "https://api.perplexity.ai/v1/responses"
}
},
"perplexity-web": {
"format": "openai",
"headers": {
Expand Down
106 changes: 106 additions & 0 deletions tests/unit/chatcore-sanitization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,112 @@ test("chatCore sanitization preserves max_output_tokens for openai-responses tar
);
});

test("chatCore preserves Chat max_tokens as max_output_tokens for Perplexity Agent Anthropic models", async () => {
const { call, result } = await invokeChatCore({
endpoint: "/v1/chat/completions",
provider: "perplexity-agent",
model: "anthropic/claude-opus-4-5",
body: {
model: "anthropic/claude-opus-4-5",
max_tokens: 32,
messages: [{ role: "user", content: "Reply with OK only." }],
},
responseFactory: () =>
new Response(
JSON.stringify({
id: "resp_pplx_agent",
object: "response",
status: "completed",
model: "anthropic/claude-opus-4-5",
output: [
{ type: "message", role: "assistant", content: [{ type: "output_text", text: "OK" }] },
],
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
),
});

assert.equal(result.success, true);
assert.equal(call.url, "https://api.perplexity.ai/v1/responses");
assert.equal(call.body.model, "anthropic/claude-opus-4-5");
assert.equal(call.body.max_output_tokens, 32);
assert.equal("max_tokens" in call.body, false);
assert.equal("max_completion_tokens" in call.body, false);
});

test("chatCore defaults max_output_tokens for Perplexity Agent Anthropic chat requests", async () => {
const { call, result } = await invokeChatCore({
endpoint: "/v1/chat/completions",
provider: "perplexity-agent",
model: "anthropic/claude-opus-4-5",
body: {
model: "anthropic/claude-opus-4-5",
messages: [{ role: "user", content: "hi" }],
},
responseFactory: () =>
new Response(
JSON.stringify({
id: "resp_pplx_agent_default_tokens",
object: "response",
status: "completed",
model: "anthropic/claude-opus-4-5",
output: [
{
type: "message",
role: "assistant",
content: [{ type: "output_text", text: "Hello!" }],
},
],
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
),
});

assert.equal(result.success, true);
assert.equal(call.url, "https://api.perplexity.ai/v1/responses");
assert.equal(call.body.max_output_tokens, 4096);
assert.equal("max_tokens" in call.body, false);
assert.equal("max_completion_tokens" in call.body, false);
});

test("chatCore defaults max_output_tokens for Perplexity Agent Kimi chat requests", async () => {
const { call, result } = await invokeChatCore({
endpoint: "/v1/chat/completions",
provider: "perplexity-agent",
model: "perplexity/kimi-k3",
body: {
model: "perplexity/kimi-k3",
messages: [{ role: "user", content: "hi" }],
},
responseFactory: () =>
new Response(
JSON.stringify({
id: "resp_pplx_agent_kimi_default_tokens",
object: "response",
status: "completed",
model: "perplexity/kimi-k3",
output: [
{
type: "message",
role: "assistant",
content: [{ type: "output_text", text: "Hello!" }],
},
],
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
),
});

assert.equal(result.success, true);
assert.equal(call.url, "https://api.perplexity.ai/v1/responses");
assert.equal(call.body.max_output_tokens, 4096);
assert.equal("max_tokens" in call.body, false);
assert.equal("max_completion_tokens" in call.body, false);
});

test("chatCore sanitization strips empty message names and filters empty tool names", async () => {
// Note: `input` field is tested separately because its presence triggers
// Responses format detection (PR #1002), which changes message handling.
Expand Down
Loading