mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-25 17:22:27 +08:00
Move first-party, sponsor and relay presets to current model IDs so a pasted Key works without editing the model list. - Claude: opus/sonnet 5 -> 5.5, haiku 4.5 -> 5.5, fable 5 -> 5.1 - GPT: gpt-5.6-sol / gpt-6-sol -> gpt-6.1-sol; add gpt-6-astra/luna - Gemini 3.6 Flash -> 3.8 Flash, 3.1 Pro preview -> 4 Argon; Grok 4.5 -> 4.7; GLM 5.1/5.2 -> 5.3 (+5.3 Flash); DeepSeek V4 Flash -> V4.1 Flash; Doubao 2.1 Pro 260628 -> 260915; StepFun Step Plan -> step-5-preview; add Kimi K3.1 Pro, Qwen3.8 Max, MiniMax M3.1 Flash where the site serves them - Drop retired models: NVIDIA moonshotai/kimi-k3 (EOL 2026-10-15, now kimi-k2.6 without unverified Codex reasoning levels), MiMo V2.5 / V2.5 Pro, Claude Sonnet 4.5, MiniMax M2.7 on Tencent Token Plan Enterprise Pro - Relay presets follow each site's own ID spelling and catalog; DIT.ai, BuyToken and hao.ai are left as configured from their docs; Soshow, SiliconFlow CN, OpenCode Zen and the Ark rolling alias stay - MoArk Codex keeps the tested Kimi-K2.7-Code; K3.1 Pro and Qwen3.8 Max stay out of its Codex catalog until their levels are verified Tests updated to the new IDs; cross-app invariants unchanged.
278 lines
8.6 KiB
TypeScript
278 lines
8.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { claudeDesktopProviderPresets } from "./claudeDesktopProviderPresets";
|
|
import { providerPresets } from "./claudeProviderPresets";
|
|
import { codexProviderPresets } from "./codexProviderPresets";
|
|
import { hermesProviderPresets } from "./hermesProviderPresets";
|
|
import { openclawProviderPresets } from "./openclawProviderPresets";
|
|
import { opencodeProviderPresets } from "./opencodeProviderPresets";
|
|
import { piProviderPresets } from "./piProviderPresets";
|
|
import { getIcon, getIconMetadata } from "../icons/extracted";
|
|
|
|
const ppioPresetCollections = [
|
|
["Claude Code", providerPresets],
|
|
["Claude Desktop", claudeDesktopProviderPresets],
|
|
["Codex", codexProviderPresets],
|
|
["OpenCode", opencodeProviderPresets],
|
|
["OpenClaw", openclawProviderPresets],
|
|
["Hermes", hermesProviderPresets],
|
|
["Pi", piProviderPresets],
|
|
] as const;
|
|
|
|
const ppioModelId = "deepseek/deepseek-v4.1-flash";
|
|
const ppioModelName = "DeepSeek V4.1 Flash";
|
|
const ppioAnthropicEndpoint = "https://api.ppio.com/anthropic";
|
|
const ppioOpenAiEndpoint = "https://api.ppio.com/openai/v1";
|
|
const ppioChatCompletionsEndpoint = `${ppioOpenAiEndpoint}/chat/completions`;
|
|
const ppioModelsEndpoint = `${ppioOpenAiEndpoint}/models`;
|
|
const ppioBrandFields = {
|
|
websiteUrl: "https://ppio.com",
|
|
apiKeyUrl: "https://ppio.com/activity/ccswitch",
|
|
category: "aggregator",
|
|
isPartner: true,
|
|
partnerPromotionKey: "ppio",
|
|
icon: "ppio",
|
|
iconColor: "#2874FF",
|
|
};
|
|
|
|
function getPpioPreset<T extends { name: string }>(presets: readonly T[]) {
|
|
return presets.find((preset) => preset.name === "PPIO");
|
|
}
|
|
|
|
describe("PPIO provider presets", () => {
|
|
it.each(ppioPresetCollections)(
|
|
"%s registers exactly one PPIO preset",
|
|
(_name, presets) => {
|
|
expect(presets.filter((preset) => preset.name === "PPIO")).toHaveLength(
|
|
1,
|
|
);
|
|
},
|
|
);
|
|
|
|
it("configures Claude Code with the native Anthropic endpoint", () => {
|
|
const claude = getPpioPreset(providerPresets)!;
|
|
expect(claude).toMatchObject({
|
|
...ppioBrandFields,
|
|
settingsConfig: {
|
|
env: {
|
|
ANTHROPIC_BASE_URL: ppioAnthropicEndpoint,
|
|
ANTHROPIC_AUTH_TOKEN: "",
|
|
ANTHROPIC_MODEL: `${ppioModelId}[1M]`,
|
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: `${ppioModelId}[1M]`,
|
|
ANTHROPIC_DEFAULT_SONNET_MODEL: `${ppioModelId}[1M]`,
|
|
ANTHROPIC_DEFAULT_OPUS_MODEL: `${ppioModelId}[1M]`,
|
|
},
|
|
},
|
|
endpointCandidates: [ppioAnthropicEndpoint],
|
|
modelsUrl: ppioModelsEndpoint,
|
|
});
|
|
});
|
|
|
|
it("configures Claude Desktop with the native Anthropic endpoint", () => {
|
|
expect(getPpioPreset(claudeDesktopProviderPresets)).toMatchObject({
|
|
...ppioBrandFields,
|
|
baseUrl: ppioAnthropicEndpoint,
|
|
mode: "proxy",
|
|
apiFormat: "anthropic",
|
|
modelRoutes: [
|
|
{
|
|
routeId: "claude-sonnet-5",
|
|
upstreamModel: ppioModelId,
|
|
labelOverride: ppioModelId,
|
|
supports1m: true,
|
|
},
|
|
],
|
|
endpointCandidates: [ppioAnthropicEndpoint],
|
|
});
|
|
});
|
|
|
|
it("configures Codex for OpenAI Chat translation and reasoning", () => {
|
|
const codex = getPpioPreset(codexProviderPresets)!;
|
|
expect(codex).toMatchObject({
|
|
...ppioBrandFields,
|
|
auth: { OPENAI_API_KEY: "" },
|
|
endpointCandidates: [ppioOpenAiEndpoint],
|
|
apiFormat: "openai_chat",
|
|
modelCatalog: [
|
|
{
|
|
model: ppioModelId,
|
|
displayName: ppioModelName,
|
|
contextWindow: 1048576,
|
|
inputModalities: ["text"],
|
|
},
|
|
],
|
|
codexChatReasoning: {
|
|
supportsThinking: true,
|
|
supportsEffort: false,
|
|
thinkingParam: "thinking",
|
|
effortParam: "none",
|
|
outputFormat: "reasoning_content",
|
|
},
|
|
});
|
|
expect(codex.config).toContain(`model = "${ppioModelId}"`);
|
|
expect(codex.config).toContain(`base_url = "${ppioOpenAiEndpoint}"`);
|
|
expect(codex.config).toContain('wire_api = "responses"');
|
|
});
|
|
|
|
it("configures OpenCode with a versioned OpenAI-compatible base", () => {
|
|
const opencode = getPpioPreset(opencodeProviderPresets)!;
|
|
expect(opencode).toMatchObject({
|
|
...ppioBrandFields,
|
|
settingsConfig: {
|
|
npm: "@ai-sdk/openai-compatible",
|
|
name: "PPIO",
|
|
options: {
|
|
baseURL: ppioOpenAiEndpoint,
|
|
apiKey: "",
|
|
setCacheKey: true,
|
|
},
|
|
models: { [ppioModelId]: { name: ppioModelName } },
|
|
},
|
|
templateValues: {
|
|
apiKey: { label: "API Key", placeholder: "", editorValue: "" },
|
|
},
|
|
});
|
|
expect(Object.keys(opencode.settingsConfig.models)).toEqual([
|
|
ppioModelId,
|
|
"moonshotai/kimi-k3.1-pro",
|
|
"zai-org/glm-5.3",
|
|
"deepseek/deepseek-v4-pro",
|
|
]);
|
|
expect(`${opencode.settingsConfig.options.baseURL}/chat/completions`).toBe(
|
|
ppioChatCompletionsEndpoint,
|
|
);
|
|
});
|
|
|
|
it("configures OpenClaw with a versioned OpenAI Chat base", () => {
|
|
const openclaw = getPpioPreset(openclawProviderPresets)!;
|
|
expect(openclaw).toMatchObject({
|
|
...ppioBrandFields,
|
|
settingsConfig: {
|
|
baseUrl: ppioOpenAiEndpoint,
|
|
apiKey: "",
|
|
api: "openai-completions",
|
|
},
|
|
templateValues: {
|
|
apiKey: { label: "API Key", placeholder: "sk-...", editorValue: "" },
|
|
},
|
|
suggestedDefaults: {
|
|
model: { primary: `ppio/${ppioModelId}` },
|
|
modelCatalog: { [`ppio/${ppioModelId}`]: { alias: ppioModelName } },
|
|
},
|
|
});
|
|
expect(
|
|
(openclaw.settingsConfig.models ?? []).map((model) => model.id),
|
|
).toEqual([
|
|
ppioModelId,
|
|
"moonshotai/kimi-k3.1-pro",
|
|
"zai-org/glm-5.3",
|
|
"deepseek/deepseek-v4-pro",
|
|
"xiaomimimo/mimo-v2.6-pro",
|
|
"qwen/qwen3.8-max",
|
|
"minimax/minimax-m3.1-flash",
|
|
"stepfun/step-5-preview",
|
|
]);
|
|
expect(openclaw.settingsConfig.models?.[0]).toMatchObject({
|
|
id: ppioModelId,
|
|
name: ppioModelName,
|
|
reasoning: true,
|
|
input: ["text"],
|
|
contextWindow: 1048576,
|
|
maxTokens: 393216,
|
|
cost: { input: 0.14, output: 0.29, cacheRead: 0.03 },
|
|
});
|
|
expect(`${openclaw.settingsConfig.baseUrl}/chat/completions`).toBe(
|
|
ppioChatCompletionsEndpoint,
|
|
);
|
|
});
|
|
|
|
it("configures Hermes with a versioned OpenAI Chat base", () => {
|
|
const hermes = getPpioPreset(hermesProviderPresets)!;
|
|
expect(hermes).toMatchObject({
|
|
...ppioBrandFields,
|
|
settingsConfig: {
|
|
name: "ppio",
|
|
base_url: ppioOpenAiEndpoint,
|
|
api_key: "",
|
|
api_mode: "chat_completions",
|
|
models: [
|
|
{
|
|
id: ppioModelId,
|
|
name: ppioModelName,
|
|
context_length: 1048576,
|
|
},
|
|
{
|
|
id: "moonshotai/kimi-k3.1-pro",
|
|
name: "Kimi K3.1 Pro",
|
|
context_length: 1048576,
|
|
},
|
|
{ id: "zai-org/glm-5.3", name: "GLM-5.3", context_length: 1000000 },
|
|
{
|
|
id: "deepseek/deepseek-v4-pro",
|
|
name: "DeepSeek V4 Pro",
|
|
context_length: 1000000,
|
|
},
|
|
],
|
|
},
|
|
suggestedDefaults: {
|
|
model: { default: ppioModelId, provider: "ppio" },
|
|
},
|
|
});
|
|
expect(`${hermes.settingsConfig.base_url}/chat/completions`).toBe(
|
|
ppioChatCompletionsEndpoint,
|
|
);
|
|
});
|
|
|
|
it("configures Pi with a versioned OpenAI Chat base", () => {
|
|
const pi = getPpioPreset(piProviderPresets)!;
|
|
expect(pi).toMatchObject({
|
|
...ppioBrandFields,
|
|
providerKey: "cc-switch-ppio",
|
|
settingsConfig: {
|
|
name: "PPIO",
|
|
baseUrl: ppioOpenAiEndpoint,
|
|
api: "openai-completions",
|
|
apiKey: "",
|
|
},
|
|
});
|
|
expect(pi.settingsConfig.models.map((model) => model.id)).toEqual([
|
|
ppioModelId,
|
|
"moonshotai/kimi-k3.1-pro",
|
|
"zai-org/glm-5.3",
|
|
]);
|
|
expect(pi.settingsConfig.models[0]).toMatchObject({
|
|
id: ppioModelId,
|
|
name: ppioModelName,
|
|
reasoning: true,
|
|
input: ["text"],
|
|
contextWindow: 1048576,
|
|
maxTokens: 393216,
|
|
thinkingLevelMap: {
|
|
minimal: null,
|
|
low: null,
|
|
medium: null,
|
|
high: "high",
|
|
max: "max",
|
|
},
|
|
compat: {
|
|
supportsStore: false,
|
|
supportsDeveloperRole: false,
|
|
maxTokensField: "max_tokens",
|
|
requiresReasoningContentOnAssistantMessages: true,
|
|
thinkingFormat: "deepseek",
|
|
},
|
|
});
|
|
expect(`${pi.settingsConfig.baseUrl}/chat/completions`).toBe(
|
|
ppioChatCompletionsEndpoint,
|
|
);
|
|
});
|
|
|
|
it("registers the PPIO brand icon", () => {
|
|
expect(getIcon("ppio")).toContain("<title>PPIO</title>");
|
|
expect(getIconMetadata("ppio")).toMatchObject({
|
|
displayName: "PPIO",
|
|
defaultColor: "#2874FF",
|
|
});
|
|
});
|
|
});
|