mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat: add Claude Fable 5 model mapping across Claude Code and Desktop
- Wire claude-fable-5 as a fourth tier on both proxy paths, with a fable -> opus -> default fallback mirroring the official downgrade. - Whitelist the fable- prefix for the Desktop 1.12603.1+ validator. - Clarify fallbackModelHint (zh/en/ja/zh-TW): a blank tier on third-party endpoints forwards the literal model name and 404s. Refs #3980, #4026, #4049.
This commit is contained in:
@@ -97,7 +97,7 @@ describe("ClaudeDesktopProviderForm", () => {
|
||||
expect(document.activeElement).toBe(currentInput);
|
||||
});
|
||||
|
||||
it("代理模式始终渲染 Sonnet / Opus / Haiku 三档(即使只配了一档)", () => {
|
||||
it("代理模式始终渲染 Sonnet / Opus / Fable / Haiku 四档(即使只配了一档)", () => {
|
||||
renderForm({
|
||||
name: "Proxy Provider",
|
||||
settingsConfig: {
|
||||
@@ -114,13 +114,13 @@ describe("ClaudeDesktopProviderForm", () => {
|
||||
},
|
||||
});
|
||||
|
||||
// 固定三档:每档各一个「菜单显示名」输入框,无论初始只配了几档。
|
||||
expect(screen.getAllByPlaceholderText("DeepSeek V4 Pro")).toHaveLength(3);
|
||||
// 固定四档:每档各一个「菜单显示名」输入框,无论初始只配了几档。
|
||||
expect(screen.getAllByPlaceholderText("DeepSeek V4 Pro")).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("代理模式初始无路由且默认路由未就绪时不渲染空三档", () => {
|
||||
it("代理模式初始无路由且默认路由未就绪时不渲染空四档", () => {
|
||||
// mock 的 getClaudeDesktopDefaultRoutes 返回 [],模拟默认路由尚未就绪。
|
||||
// 修复前:normalizeProxyRows([]) 会渲染 3 条空行并把 routes.length 撑到 3,
|
||||
// 修复前:normalizeProxyRows([]) 会渲染空行并把 routes.length 撑起来,
|
||||
// 永久挡住 seed effect 的默认路由回填。修复后应保持空、等待 seed。
|
||||
renderForm({
|
||||
name: "Proxy Provider",
|
||||
@@ -139,7 +139,7 @@ describe("ClaudeDesktopProviderForm", () => {
|
||||
expect(screen.queryAllByPlaceholderText("DeepSeek V4 Pro")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("保存模型映射时补齐固定三档并把留空档回填为 Sonnet 模型", async () => {
|
||||
it("保存模型映射时补齐固定四档并把留空档回填为 Sonnet 模型", async () => {
|
||||
const onSubmit = vi.fn();
|
||||
renderForm(
|
||||
{
|
||||
@@ -166,19 +166,25 @@ describe("ClaudeDesktopProviderForm", () => {
|
||||
|
||||
await waitFor(() => expect(onSubmit).toHaveBeenCalled());
|
||||
const submitted = onSubmit.mock.calls[0][0];
|
||||
// claude-old 迁移到 Sonnet;留空的 Opus / Haiku 回填为 Sonnet 的上游模型,
|
||||
// 保证落库三档齐全,子 agent 调用的 Haiku 始终可解析。
|
||||
// claude-old 迁移到 Sonnet;留空的 Opus / Fable / Haiku 回填为 Sonnet 的
|
||||
// 上游模型,保证落库四档齐全,子 agent 调用的各档始终可解析。
|
||||
expect(submitted.meta.claudeDesktopModelRoutes).toMatchObject({
|
||||
"claude-sonnet-4-6": {
|
||||
model: "upstream-old",
|
||||
labelOverride: "upstream-old",
|
||||
},
|
||||
"claude-opus-4-8": { model: "upstream-old" },
|
||||
"claude-fable-5": { model: "upstream-old" },
|
||||
"claude-haiku-4-5": { model: "upstream-old" },
|
||||
});
|
||||
expect(
|
||||
Object.keys(submitted.meta.claudeDesktopModelRoutes).sort(),
|
||||
).toEqual(["claude-haiku-4-5", "claude-opus-4-8", "claude-sonnet-4-6"]);
|
||||
).toEqual([
|
||||
"claude-fable-5",
|
||||
"claude-haiku-4-5",
|
||||
"claude-opus-4-8",
|
||||
"claude-sonnet-4-6",
|
||||
]);
|
||||
});
|
||||
|
||||
it("回填空档时继承 Sonnet 的 1M 声明", async () => {
|
||||
|
||||
@@ -83,6 +83,8 @@ const renderCopilotForm = (overrides: Partial<ClaudeFormFieldsProps> = {}) => {
|
||||
defaultSonnetModelName: "Claude Sonnet",
|
||||
defaultOpusModel: "",
|
||||
defaultOpusModelName: "",
|
||||
defaultFableModel: "",
|
||||
defaultFableModelName: "",
|
||||
onModelChange: vi.fn(),
|
||||
speedTestEndpoints: [],
|
||||
apiFormat: "anthropic",
|
||||
|
||||
Reference in New Issue
Block a user