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.
332 lines
12 KiB
TypeScript
332 lines
12 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
|
||
import { providerPresets } from "@/config/claudeProviderPresets";
|
||
import { codexProviderPresets } from "@/config/codexProviderPresets";
|
||
import { mcodeProviderPresets } from "@/config/mcodeProviderPresets";
|
||
import { openclawProviderPresets } from "@/config/openclawProviderPresets";
|
||
import { opencodeProviderPresets } from "@/config/opencodeProviderPresets";
|
||
import { piProviderPresets } from "@/config/piProviderPresets";
|
||
import { getIcon, hasIcon } from "@/icons/extracted";
|
||
|
||
const WEBSITE_URL = "https://moark.com";
|
||
const API_KEY_URL = "https://moark.com/dashboard/tokens";
|
||
const DEFAULT_MODEL = "deepseek-v4.1-flash";
|
||
|
||
describe("MoArk (模力方舟) provider presets", () => {
|
||
it("uses the Anthropic-native root endpoint for Claude Code", () => {
|
||
const preset = providerPresets.find((item) => item.name === "模力方舟");
|
||
|
||
expect(preset).toBeDefined();
|
||
expect(preset?.websiteUrl).toBe(WEBSITE_URL);
|
||
expect(preset?.apiKeyUrl).toBe(API_KEY_URL);
|
||
expect(preset?.category).toBe("aggregator");
|
||
expect(preset?.endpointCandidates).toEqual(["https://moark.com/anthropic"]);
|
||
expect(preset?.icon).toBe("moark");
|
||
|
||
const env = (preset?.settingsConfig as { env: Record<string, string> }).env;
|
||
expect(env.ANTHROPIC_BASE_URL).toBe("https://moark.com/anthropic");
|
||
expect(env.ANTHROPIC_AUTH_TOKEN).toBe("");
|
||
// 各档预勾 1M(模型名带 [1M] 标记)
|
||
expect(env.ANTHROPIC_MODEL).toBe(`${DEFAULT_MODEL}[1M]`);
|
||
expect(env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe(`${DEFAULT_MODEL}[1M]`);
|
||
expect(env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe(`${DEFAULT_MODEL}[1M]`);
|
||
expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe(`${DEFAULT_MODEL}[1M]`);
|
||
});
|
||
|
||
it("uses the OpenAI-compatible Responses endpoint for Codex", () => {
|
||
const preset = codexProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
|
||
expect(preset).toBeDefined();
|
||
expect(preset?.websiteUrl).toBe(WEBSITE_URL);
|
||
expect(preset?.apiKeyUrl).toBe(API_KEY_URL);
|
||
expect(preset?.category).toBe("aggregator");
|
||
expect(preset?.endpointCandidates).toEqual(["https://moark.com/v1"]);
|
||
expect(preset?.icon).toBe("moark");
|
||
expect(preset?.auth).toEqual({ OPENAI_API_KEY: "" });
|
||
expect(preset?.config).toContain('model = "deepseek-v4.1-flash"');
|
||
expect(preset?.config).toContain('base_url = "https://moark.com/v1"');
|
||
expect(preset?.config).toContain('wire_api = "responses"');
|
||
});
|
||
|
||
it("declares the Codex model catalog so /model lists every MoArk model", () => {
|
||
const preset = codexProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
const catalog = preset?.modelCatalog ?? [];
|
||
|
||
// 缺 modelCatalog 时 Codex 退回通用元数据,/model 只会列出默认模型。
|
||
expect(catalog.map((model) => model.model)).toEqual([
|
||
DEFAULT_MODEL,
|
||
"DeepSeek-V4-Pro-0813",
|
||
"GLM-5.3",
|
||
"GLM-5.3-Flash",
|
||
"Kimi-K2.7-Code",
|
||
]);
|
||
|
||
// 目录首行 = 默认模型,必须与 config.toml 的 model 一致。
|
||
expect(preset?.config).toContain(`model = "${catalog[0]?.model}"`);
|
||
|
||
for (const model of catalog) {
|
||
expect(
|
||
model.contextWindow,
|
||
`${model.model} contextWindow`,
|
||
).toBeGreaterThan(0);
|
||
expect(model.inputModalities, `${model.model} inputModalities`).toContain(
|
||
"text",
|
||
);
|
||
}
|
||
// Kimi K2.7 Code 支持图像输入。
|
||
expect(
|
||
catalog.find((model) => model.model === "Kimi-K2.7-Code")?.inputModalities,
|
||
).toEqual(["text", "image"]);
|
||
});
|
||
|
||
it("keeps Codex capacities in step with the OpenClaw preset", () => {
|
||
const codex = codexProviderPresets.find((item) => item.name === "模力方舟");
|
||
const openclaw = openclawProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
const openclawById = new Map(
|
||
(openclaw?.settingsConfig.models ?? []).map((model) => [model.id, model]),
|
||
);
|
||
|
||
for (const model of codex?.modelCatalog ?? []) {
|
||
const counterpart = openclawById.get(model.model);
|
||
expect(
|
||
counterpart,
|
||
`${model.model} exists in the OpenClaw preset`,
|
||
).toBeDefined();
|
||
expect(model.contextWindow, `${model.model} contextWindow`).toBe(
|
||
counterpart?.contextWindow,
|
||
);
|
||
}
|
||
});
|
||
|
||
it("uses the Codex NativeResponses profile for the direct gateway", () => {
|
||
const preset = codexProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
|
||
// moark.com 不在 CODEX_NATIVE_RESPONSES_HOSTS 兜底名单里,而这条路径是原生
|
||
// Responses 直连、没有代理去改写 custom→function。不显式声明 apiFormat 会让
|
||
// resolve_codex_catalog_tool_profile 落成 ProxyChat,即克隆 gpt-5.5 模板
|
||
// (GPT-5 harness + 自由格式 apply_patch),给聚合平台下发它未承诺的能力。
|
||
expect(preset?.apiFormat).toBe("openai_responses");
|
||
});
|
||
|
||
it("declares only platform- or vendor-verified Codex reasoning levels", () => {
|
||
const preset = codexProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
const levels = Object.fromEntries(
|
||
(preset?.modelCatalog ?? []).map((model) => [
|
||
model.model,
|
||
model.reasoningLevels,
|
||
]),
|
||
);
|
||
|
||
// 实测自 https://moark.com/v1/responses:非法 reasoning_effort 触发的 400
|
||
// 里厂商给出的明文枚举。
|
||
expect(levels["DeepSeek-V4-Pro-0813"]).toEqual([
|
||
"low",
|
||
"medium",
|
||
"high",
|
||
"xhigh",
|
||
"max",
|
||
]);
|
||
expect(levels["GLM-5.3"]).toEqual([
|
||
"none",
|
||
"low",
|
||
"medium",
|
||
"high",
|
||
"xhigh",
|
||
"max",
|
||
]);
|
||
expect(levels["Kimi-K2.7-Code"]).toEqual([
|
||
"none",
|
||
"minimal",
|
||
"low",
|
||
"medium",
|
||
"high",
|
||
"xhigh",
|
||
]);
|
||
// 2026-10-24 目录刷新:qwen3-coder-plus 已下架;kimi-k3.1-pro / qwen3.8-max
|
||
// 的档位平台未自证、官方档位与 model_reasoning_effort = "high" 不兼容,
|
||
// 暂不进 Codex 目录(Pi / OpenCode / OpenClaw 预设照常收录)。
|
||
// MoArk 不校验该字段,退回 DeepSeek 官方目录的档位。
|
||
expect(levels[DEFAULT_MODEL]).toEqual(["low", "high", "max"]);
|
||
// 留空不等于"没有档位":后端会把未填写理解为沿用原生模板的 none/high 两档、
|
||
// 默认 high,Codex 会给出未经平台证实的选择器。
|
||
|
||
// 只有单档时 Codex 直接应用、不显示选择界面;支持思考的模型必须保留多档
|
||
// 且每档都含 high,后端才会保留模板默认 high,与 config 的
|
||
// model_reasoning_effort = "high" 一致。目录里五款模型都支持思考。
|
||
const thinkingModels = preset?.modelCatalog ?? [];
|
||
expect(thinkingModels).toHaveLength(5);
|
||
for (const model of thinkingModels) {
|
||
expect(model.reasoningLevels, `${model.model} keeps high`).toContain(
|
||
"high",
|
||
);
|
||
expect(
|
||
model.reasoningLevels?.length,
|
||
`${model.model} still offers a choice`,
|
||
).toBeGreaterThan(1);
|
||
}
|
||
expect(preset?.config).toContain('model_reasoning_effort = "high"');
|
||
});
|
||
|
||
it("uses the OpenAI-compatible endpoint for Pi", () => {
|
||
const preset = piProviderPresets.find((item) => item.name === "模力方舟");
|
||
|
||
expect(preset).toBeDefined();
|
||
expect(preset?.websiteUrl).toBe(WEBSITE_URL);
|
||
expect(preset?.apiKeyUrl).toBe(API_KEY_URL);
|
||
expect(preset?.category).toBe("aggregator");
|
||
expect(preset?.icon).toBe("moark");
|
||
expect(preset?.providerKey).toBe("cc-switch-moark");
|
||
expect(preset?.settingsConfig.baseUrl).toBe("https://api.moark.com/v1");
|
||
expect(preset?.settingsConfig.api).toBe("openai-completions");
|
||
expect(preset?.settingsConfig.apiKey).toBe("");
|
||
|
||
const modelIds = preset?.settingsConfig.models.map((model) => model.id);
|
||
expect(modelIds).toEqual(
|
||
expect.arrayContaining([
|
||
DEFAULT_MODEL,
|
||
"DeepSeek-V4-Pro-0813",
|
||
"GLM-5.3",
|
||
"GLM-5.3-Flash",
|
||
"kimi-k3.1-pro",
|
||
"qwen3.8-max",
|
||
]),
|
||
);
|
||
});
|
||
|
||
it("uses the OpenAI-compatible endpoint for OpenCode", () => {
|
||
const preset = opencodeProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
const models = preset?.settingsConfig.models ?? {};
|
||
|
||
expect(preset).toBeDefined();
|
||
expect(preset?.websiteUrl).toBe(WEBSITE_URL);
|
||
expect(preset?.apiKeyUrl).toBe(API_KEY_URL);
|
||
expect(preset?.category).toBe("aggregator");
|
||
expect(preset?.icon).toBe("moark");
|
||
expect(preset?.settingsConfig.npm).toBe("@ai-sdk/openai-compatible");
|
||
expect(preset?.settingsConfig.options?.baseURL).toBe(
|
||
"https://api.moark.com/v1",
|
||
);
|
||
expect(models).toHaveProperty(DEFAULT_MODEL);
|
||
expect(models).toHaveProperty("kimi-k3.1-pro");
|
||
expect(models[DEFAULT_MODEL]?.name).toBe("DeepSeek V4.1 Flash");
|
||
|
||
// OpenCode only offers thinking variants for models flagged as reasoning.
|
||
// The former non-thinking Qwen3 Coder Plus (left unflagged) was replaced by
|
||
// Qwen3.8 Max, which thinks, so every listed model is flagged now.
|
||
for (const id of [
|
||
DEFAULT_MODEL,
|
||
"DeepSeek-V4-Pro-0813",
|
||
"GLM-5.3",
|
||
"GLM-5.3-Flash",
|
||
"kimi-k3.1-pro",
|
||
"qwen3.8-max",
|
||
]) {
|
||
expect(models[id]?.reasoning, `${id} reasoning`).toBe(true);
|
||
}
|
||
});
|
||
|
||
it("uses the OpenAI Completions endpoint for OpenClaw", () => {
|
||
const preset = openclawProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
const modelIds = (preset?.settingsConfig.models ?? []).map(
|
||
(model) => model.id,
|
||
);
|
||
|
||
expect(preset).toBeDefined();
|
||
expect(preset?.websiteUrl).toBe(WEBSITE_URL);
|
||
expect(preset?.apiKeyUrl).toBe(API_KEY_URL);
|
||
expect(preset?.category).toBe("aggregator");
|
||
expect(preset?.icon).toBe("moark");
|
||
expect(preset?.settingsConfig.baseUrl).toBe("https://api.moark.com/v1");
|
||
expect(preset?.settingsConfig.api).toBe("openai-completions");
|
||
expect(modelIds).toEqual(
|
||
expect.arrayContaining([
|
||
DEFAULT_MODEL,
|
||
"DeepSeek-V4-Pro-0813",
|
||
"GLM-5.3",
|
||
"kimi-k3.1-pro",
|
||
]),
|
||
);
|
||
expect(preset?.suggestedDefaults?.model).toEqual({
|
||
primary: `moark/${DEFAULT_MODEL}`,
|
||
});
|
||
expect(preset?.suggestedDefaults?.modelCatalog).toHaveProperty(
|
||
`moark/${DEFAULT_MODEL}`,
|
||
);
|
||
});
|
||
|
||
it("declares positive capacity limits for every OpenCode model", () => {
|
||
const preset = opencodeProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
const models = preset?.settingsConfig.models ?? {};
|
||
|
||
// OpenCode treats a missing limit as 0 and then skips auto-compaction
|
||
// (`isOverflow` returns false when `limit.context === 0`), so every model
|
||
// of a custom provider key must carry explicit capacity numbers.
|
||
expect(Object.keys(models)).toHaveLength(6);
|
||
for (const [id, model] of Object.entries(models)) {
|
||
expect(model.limit?.context, `${id} limit.context`).toBeGreaterThan(0);
|
||
expect(model.limit?.output, `${id} limit.output`).toBeGreaterThan(0);
|
||
}
|
||
});
|
||
|
||
it("keeps OpenCode capacities consistent with the OpenClaw preset", () => {
|
||
const opencode = opencodeProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
const openclaw = openclawProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
const openclawById = new Map(
|
||
(openclaw?.settingsConfig.models ?? []).map((model) => [model.id, model]),
|
||
);
|
||
|
||
for (const [id, model] of Object.entries(
|
||
opencode?.settingsConfig.models ?? {},
|
||
)) {
|
||
const counterpart = openclawById.get(id);
|
||
expect(counterpart, `${id} exists in the OpenClaw preset`).toBeDefined();
|
||
expect(model.limit?.context, `${id} context`).toBe(
|
||
counterpart?.contextWindow,
|
||
);
|
||
expect(model.limit?.output, `${id} output`).toBe(counterpart?.maxTokens);
|
||
}
|
||
});
|
||
|
||
it("inherits the Pi preset for MiniMax Code", () => {
|
||
const preset = mcodeProviderPresets.find(
|
||
(item) => item.name === "模力方舟",
|
||
);
|
||
|
||
expect(preset).toBeDefined();
|
||
expect(preset?.settingsConfig.kind).toBe("custom");
|
||
expect(preset?.settingsConfig.api).toBe("openai-completions");
|
||
expect(preset?.settingsConfig.options.baseURL).toBe(
|
||
"https://api.moark.com/v1",
|
||
);
|
||
expect(Object.keys(preset?.settingsConfig.models ?? {})).toContain(
|
||
DEFAULT_MODEL,
|
||
);
|
||
});
|
||
|
||
it("registers the MoArk mark in the icon registry", () => {
|
||
expect(hasIcon("moark")).toBe(true);
|
||
expect(getIcon("moark")).toContain("<title>MoArk</title>");
|
||
expect(getIcon("moark")).toContain('viewBox="0 0 88 88"');
|
||
});
|
||
});
|