Align Claude Desktop model mapping with Claude Code three-role tiers

Claude Desktop's 3P validation only accepts claude-{sonnet,opus,haiku}-*
role IDs, so providers must map every tier. Bring the Desktop mapping flow
in line with Claude Code and fix the fallout that broke sub-agent Haiku calls.

- Proxy form: replace the dynamic route list with fixed Sonnet/Opus/Haiku
  tiers; blank tiers backfill from the first filled tier (Sonnet first) on
  submit and inherit its supports1m flag
- Backend: add a role-keyword fallback in map_proxy_request_model so dated
  official names (e.g. claude-haiku-4-5-20251001) resolve to the right tier,
  guarded by is_claude_safe_model_id so [1m]-suffixed IDs stay rejected
- Tighten is_claude_safe_model_id / isClaudeSafeRoute to reject degenerate
  role IDs like "claude-sonnet-"
- Fix the seed-effect race where normalizing empty routes to three blank
  tiers blocked the default-route backfill
- Sync switch hints, placeholders, and the zh/en/ja/zh-TW locales to the
  three-role-ID rule
- Update zh/en/ja user manual (2.1, 2.6), calling out legacy Claude IDs
  (claude-3-5-sonnet-...) as a rejected example

Tests: 282 frontend + 34 backend claude_desktop; typecheck, clippy, fmt clean.
This commit is contained in:
Jason
2026-05-29 22:27:47 +08:00
parent 058c9fb8e5
commit 94cc3d103a
13 changed files with 574 additions and 363 deletions
@@ -118,44 +118,6 @@ const ANTHROPIC_CLAUDE_ROUTE_PREFIX = "anthropic/claude-";
const LEGACY_ONE_M_MARKER = "[1m]";
const ROLE_ROUTE_IDS = CLAUDE_DESKTOP_ROLE_ROUTE_IDS;
const ROLE_ORDER: RouteRole[] = ["sonnet", "opus", "haiku"];
const NON_ANTHROPIC_ROUTE_MARKERS = [
"ark-code",
"astron",
"command-r",
"deepseek",
"doubao",
"gemini",
"gemma",
"glm",
"gpt",
"grok",
"hermes",
"hy3",
"kimi",
"lfm",
"llama",
"longcat",
"mimo",
"minimax",
"mistral",
"mixtral",
"moonshot",
"nemotron",
"openai",
"qianfan",
"qwen",
"stepfun",
"seed-",
"hunyuan",
"nova-",
"ernie",
"codex",
"abab",
"jamba",
"arctic",
"solar",
"mercury",
];
function envString(
settingsConfig: Record<string, unknown> | undefined,
@@ -231,21 +193,37 @@ function initialRouteRows(
});
}
// Proxy 模式对齐 Claude Code:固定 Sonnet / Opus / Haiku 三档。
// 把任意来源的 route 行按角色归类到固定三槽(缺档留空),保证 UI 永远三行、
// 用户不会漏配 Haiku 导致子 agent 找不到模型。
function normalizeProxyRows(rows: RouteRow[]): RouteRow[] {
return ROLE_ORDER.map((role) => {
const match = rows.find(
(row) => row.route.trim() && routeRoleFromId(row.route) === role,
);
return createRouteRow({
route: ROLE_ROUTE_IDS[role],
model: match?.model ?? "",
labelOverride: match?.labelOverride ?? "",
supports1m: match?.supports1m ?? false,
});
});
}
function isClaudeSafeRoute(route: string) {
const normalized = route.trim().toLowerCase();
if (normalized.includes(LEGACY_ONE_M_MARKER)) return false;
const hasAllowedShape =
[CLAUDE_ROUTE_PREFIX, ANTHROPIC_CLAUDE_ROUTE_PREFIX].some(
(prefix) =>
normalized.startsWith(prefix) && normalized.length > prefix.length,
) ||
["sonnet", "opus", "haiku"].includes(normalized) ||
normalized.startsWith("sonnet-") ||
normalized.startsWith("opus-") ||
normalized.startsWith("haiku-");
return (
hasAllowedShape &&
!NON_ANTHROPIC_ROUTE_MARKERS.some((marker) => normalized.includes(marker))
const routeTail = normalized.startsWith(ANTHROPIC_CLAUDE_ROUTE_PREFIX)
? normalized.slice(ANTHROPIC_CLAUDE_ROUTE_PREFIX.length)
: normalized.startsWith(CLAUDE_ROUTE_PREFIX)
? normalized.slice(CLAUDE_ROUTE_PREFIX.length)
: "";
// 角色前缀后必须还有实际模型标识,拒绝 claude-sonnet- 这类退化值
// (否则会写入 profile 并触发 Claude Desktop fail-all 拒收整组)。
return ["sonnet-", "opus-", "haiku-"].some(
(prefix) =>
routeTail.startsWith(prefix) && routeTail.length > prefix.length,
);
}
@@ -263,30 +241,6 @@ function defaultRouteRows(
);
}
function nextRouteRow(current: RouteRow[], defaults: RouteRow[]): RouteRow {
const defaultRow =
defaults.find(
(route) => !current.some((existing) => existing.route === route.route),
) ?? null;
if (defaultRow) {
return createRouteRow({
route: defaultRow.route,
model: defaultRow.model,
labelOverride: defaultRow.labelOverride,
supports1m: defaultRow.supports1m,
});
}
const usedRoutes = new Set(current.map((route) => route.route));
return createRouteRow({
route: routeIdForRole("sonnet", usedRoutes),
model: "",
labelOverride: "",
supports1m: true,
});
}
export function ClaudeDesktopProviderForm({
submitLabel,
onSubmit,
@@ -334,9 +288,15 @@ export function ClaudeDesktopProviderForm({
providerType?: string;
requiresOAuth?: boolean;
} | null>(null);
const [routes, setRoutes] = useState<RouteRow[]>(() =>
initialRouteRows(initialData?.meta?.claudeDesktopModelRoutes),
);
const [routes, setRoutes] = useState<RouteRow[]>(() => {
const rows = initialRouteRows(initialData?.meta?.claudeDesktopModelRoutes);
// proxy 模式归一化成固定三档;但初始无任何 route 时保持空数组,交给 seed
// effect 用默认路由回填(默认 1M 声明、ANTHROPIC_MODEL 预填),避免过早
// normalize 成空三档把 routes.length 撑到 3、永久挡住 seed。
return initialMode === "proxy" && rows.length > 0
? normalizeProxyRows(rows)
: rows;
});
const didSeedDefaultRoutes = useRef(
Object.keys(initialData?.meta?.claudeDesktopModelRoutes ?? {}).length > 0,
);
@@ -435,13 +395,15 @@ export function ClaudeDesktopProviderForm({
setMode(preset.mode);
if (preset.mode === "proxy" && preset.modelRoutes) {
setRoutes(
preset.modelRoutes.map((r) =>
createRouteRow({
route: r.routeId,
model: r.upstreamModel,
labelOverride: r.labelOverride ?? "",
supports1m: r.supports1m,
}),
normalizeProxyRows(
preset.modelRoutes.map((r) =>
createRouteRow({
route: r.routeId,
model: r.upstreamModel,
labelOverride: r.labelOverride ?? "",
supports1m: r.supports1m,
}),
),
),
);
} else {
@@ -485,28 +447,26 @@ export function ClaudeDesktopProviderForm({
);
};
const updateRouteRole = (index: number, role: RouteRole) => {
setRoutes((current) => {
const usedRoutes = new Set(
current
.filter((_, i) => i !== index)
.map((row) => row.route)
.filter(Boolean),
);
const route = routeIdForRole(role, usedRoutes);
return current.map((row, i) => (i === index ? { ...row, route } : row));
});
};
const handleModelMappingChange = (checked: boolean) => {
setMode(checked ? "proxy" : "direct");
if (checked) {
// 切到 proxy:归一化成固定 Sonnet / Opus / Haiku 三档;
// 若当前无路由则以后端默认路由作为来源(保留 Sonnet 默认模型)。
setRoutes((current) => {
if (current.length > 0 || defaultProxyRouteRows.length === 0) {
// 默认路由(默认 1M 声明、ANTHROPIC_MODEL 预填)异步加载完成前,若当前
// 无路由则保持空数组,交给 seed effect 在加载后回填;不要过早 normalize
// 成空三档(会把 routes.length 撑到 3、永久挡住 seed)。
if (current.length === 0 && defaultProxyRouteRows.length === 0) {
return current;
}
didSeedDefaultRoutes.current = true;
return defaultProxyRouteRows;
const useDefaults =
current.length === 0 && defaultProxyRouteRows.length > 0;
if (useDefaults) {
didSeedDefaultRoutes.current = true;
}
return normalizeProxyRows(
useDefaults ? defaultProxyRouteRows : current,
);
});
}
};
@@ -522,7 +482,7 @@ export function ClaudeDesktopProviderForm({
}
didSeedDefaultRoutes.current = true;
setRoutes(defaultProxyRouteRows);
setRoutes(normalizeProxyRows(defaultProxyRouteRows));
}, [defaultProxyRouteRows, mode, routes.length]);
const handleFetchModels = async () => {
@@ -614,19 +574,11 @@ export function ClaudeDesktopProviderForm({
.filter((route) => route.route || route.model);
if (mode === "proxy") {
const invalid = routeEntries.find(
(route) =>
!route.route || !route.model || !isClaudeSafeRoute(route.route),
);
if (invalid) {
toast.error(
t("claudeDesktop.routeInvalid", {
defaultValue: "请填写 Desktop 显示模型和实际请求模型",
}),
);
return;
}
if (routeEntries.length === 0) {
// 固定三档(Sonnet / Opus / Haiku),route_id 由 UI 生成、恒合法,
// 因此只要求至少填一个实际请求模型;留空档继承第一个已填档(Sonnet 优先),
// 对齐 Claude Code 的兜底,保证落库三档齐全、子 agent 不会找不到 Haiku。
const primary = routeEntries.find((route) => route.model);
if (!primary) {
toast.error(
t("claudeDesktop.routesRequired", {
defaultValue: "至少填写一个模型映射",
@@ -634,6 +586,19 @@ export function ClaudeDesktopProviderForm({
);
return;
}
for (const route of routeEntries) {
if (!route.model) {
route.model = primary.model;
if (!route.labelOverride) {
route.labelOverride = primary.labelOverride || primary.model;
}
// 回填的是同一个上游模型,1M 能力声明应与 primary 一致,
// 避免同模型在不同档声明不同 1M(除非该档用户已显式勾选)。
if (!route.supports1m) {
route.supports1m = primary.supports1m;
}
}
}
} else {
const invalid = routeEntries.find(
(route) => !route.route || !isClaudeSafeRoute(route.route),
@@ -642,7 +607,7 @@ export function ClaudeDesktopProviderForm({
toast.error(
t("claudeDesktop.directModelInvalid", {
defaultValue:
"直连模型必须使用 claude-* / anthropic/claude-* 模型名",
"直连模型必须使用 Claude Desktop 可识别的 Sonnet / Opus / Haiku 模型名",
}),
);
return;
@@ -668,7 +633,7 @@ export function ClaudeDesktopProviderForm({
Record<string, ClaudeDesktopModelRoute>
>((acc, route) => {
acc[route.route] = {
model: route.model || route.route,
model: mode === "direct" ? route.route : route.model || route.route,
labelOverride:
route.labelOverride || (mode === "proxy" ? route.model : undefined),
supports1m: route.supports1m || undefined,
@@ -838,11 +803,11 @@ export function ClaudeDesktopProviderForm({
{needsModelMapping
? t("claudeDesktop.modelMappingOnHint", {
defaultValue:
"Claude Desktop 目前对模型 ID 进行了限制,如果您的供应商提供的模型不是 Claude 系列模型,则需要打开本开关,并在使用过程中保持本地路由开启。",
"Claude Desktop 只接受 claude-sonnet-* / claude-opus-* / claude-haiku-* 三档角色 ID。开启后 CC Switch 会把这三档映射到供应商的实际模型,并在使用期间保持本地路由开启。",
})
: t("claudeDesktop.modelMappingOffHint", {
defaultValue:
"适合供应商已经暴露并接受 claude-* / anthropic/claude-* 模型名的 Anthropic Messages API;请求会由 Claude Desktop 直连供应商。",
"仅当供应商直接接受 Claude Desktop 可识别的三档角色 IDclaude-sonnet-* / claude-opus-* / claude-haiku-*)时才适用直连;其他模型名(含 claude-3-5-sonnet-… 等旧式 ID)请打开此开关走映射。",
})}
</p>
</div>
@@ -905,26 +870,35 @@ export function ClaudeDesktopProviderForm({
defaultValue: "模型映射",
})}
</Label>
{renderActionButtons(
() =>
setRoutes((current) => [
...current,
nextRouteRow(current, defaultProxyRouteRows),
]),
t("claudeDesktop.addRoute", {
defaultValue: "添加模型",
}),
{!usesManagedOAuth && (
<Button
type="button"
variant="outline"
size="sm"
onClick={handleFetchModels}
disabled={isFetchingModels}
className="h-7 gap-1"
>
{isFetchingModels ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<Download className="h-3.5 w-3.5" />
)}
{t("providerForm.fetchModels", {
defaultValue: "获取模型",
})}
</Button>
)}
</div>
<p className="text-xs leading-relaxed text-muted-foreground">
{t("claudeDesktop.routeMapHint", {
defaultValue:
"选择模型角色后,CC Switch 会自动生成 Claude Desktop 兼容路由;菜单显示名可写 DeepSeek、Kimi 等品牌模型,实际请求模型按右侧填写内容发送。",
"为 Sonnet、Opus、Haiku 三档分别填写实际请求模型;菜单显示名可写 DeepSeek、Kimi 等品牌名。留空的档会自动沿用 Sonnet(或第一个已填档)的模型,确保子 agent 调用的 Haiku 始终可用。",
})}
</p>
</div>
<div className="hidden grid-cols-[140px_1fr_1fr_116px_36px] gap-2 px-1 text-xs font-medium text-muted-foreground md:grid">
<div className="hidden grid-cols-[140px_1fr_1fr_116px] gap-2 px-1 text-xs font-medium text-muted-foreground md:grid">
<span>
{t("claudeDesktop.routeModelLabel", {
defaultValue: "模型角色",
@@ -945,101 +919,75 @@ export function ClaudeDesktopProviderForm({
defaultValue: "声明支持 1M",
})}
</span>
<span />
</div>
{routes.map((route, index) => (
<div
key={route.rowId}
className="grid grid-cols-1 gap-2 md:grid-cols-[140px_1fr_1fr_116px_36px]"
>
<Select
value={routeRoleFromId(route.route)}
onValueChange={(value) =>
updateRouteRole(index, value as RouteRole)
}
>
<SelectTrigger className="h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="sonnet">
{t("claudeDesktop.routeRoleSonnet", {
defaultValue: "Sonnet",
})}
</SelectItem>
<SelectItem value="opus">
{t("claudeDesktop.routeRoleOpus", {
defaultValue: "Opus",
})}
</SelectItem>
<SelectItem value="haiku">
{t("claudeDesktop.routeRoleHaiku", {
defaultValue: "Haiku",
})}
</SelectItem>
</SelectContent>
</Select>
<Input
value={route.labelOverride}
onChange={(event) =>
updateRoute(index, {
labelOverride: event.target.value,
{routes.map((route, index) => {
const role = routeRoleFromId(route.route);
const roleLabel =
role === "opus"
? t("claudeDesktop.routeRoleOpus", {
defaultValue: "Opus",
})
}
placeholder="DeepSeek V4 Pro"
/>
<div className="flex gap-1">
: role === "haiku"
? t("claudeDesktop.routeRoleHaiku", {
defaultValue: "Haiku",
})
: t("claudeDesktop.routeRoleSonnet", {
defaultValue: "Sonnet",
});
return (
<div
key={route.rowId}
className="grid grid-cols-1 gap-2 md:grid-cols-[140px_1fr_1fr_116px]"
>
<div className="flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm font-medium text-muted-foreground">
{roleLabel}
</div>
<Input
value={route.model}
value={route.labelOverride}
onChange={(event) =>
updateRoute(index, { model: event.target.value })
updateRoute(index, {
labelOverride: event.target.value,
})
}
placeholder="kimi-k2 / deepseek-chat"
className="flex-1"
placeholder="DeepSeek V4 Pro"
/>
{fetchedModels.length > 0 && (
<ModelDropdown
models={fetchedModels}
onSelect={(id) =>
<div className="flex gap-1">
<Input
value={route.model}
onChange={(event) =>
updateRoute(index, { model: event.target.value })
}
placeholder="kimi-k2 / deepseek-chat"
className="flex-1"
/>
{fetchedModels.length > 0 && (
<ModelDropdown
models={fetchedModels}
onSelect={(id) =>
updateRoute(index, {
model: id,
labelOverride: route.labelOverride || id,
})
}
/>
)}
</div>
<label className="flex h-9 items-center gap-2 text-sm text-muted-foreground">
<Checkbox
checked={route.supports1m}
onCheckedChange={(checked) =>
updateRoute(index, {
model: id,
labelOverride: route.labelOverride || id,
route:
route.route ||
routeIdForRole(
"sonnet",
new Set(routes.map((row) => row.route)),
),
supports1m: checked === true,
})
}
/>
)}
{t("claudeDesktop.supports1mShort", {
defaultValue: "1M",
})}
</label>
</div>
<label className="flex h-9 items-center gap-2 text-sm text-muted-foreground">
<Checkbox
checked={route.supports1m}
onCheckedChange={(checked) =>
updateRoute(index, { supports1m: checked === true })
}
/>
{t("claudeDesktop.supports1mShort", {
defaultValue: "1M",
})}
</label>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() =>
setRoutes((current) =>
current.filter((_, i) => i !== index),
)
}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
);
})}
</div>
</div>
)}
@@ -1081,7 +1029,7 @@ export function ClaudeDesktopProviderForm({
<p className="flex-1 text-xs leading-relaxed text-muted-foreground">
{t("claudeDesktop.directModelListHint", {
defaultValue:
"仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 claude-* 模型名时填写;这些模型名会原样发送给供应商。",
"仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 Sonnet / Opus / Haiku 模型名时填写;勾选 1M 会向 Claude Desktop 声明支持 1M 上下文。",
})}
</p>
{renderActionButtons(
@@ -1116,7 +1064,7 @@ export function ClaudeDesktopProviderForm({
route: event.target.value,
})
}
placeholder="claude-deepseek-chat"
placeholder="claude-sonnet-4-6"
className="flex-1"
/>
{fetchedModels.length > 0 && (
+6 -6
View File
@@ -187,10 +187,10 @@
"modeDirect": "Direct",
"modeProxy": "Requires routing",
"modelMappingToggle": "Needs model mapping",
"modelMappingOffHint": "If your provider does not offer native Claude series models, please enable this switch.",
"modelMappingOnHint": "Claude Desktop currently restricts model IDs. If your provider offers non-Claude models, enable this switch and keep local routing running while in use.",
"modelMappingOffHint": "Direct mode works only when the provider accepts Claude Desktop's three role IDs (claude-sonnet-* / claude-opus-* / claude-haiku-*); for any other model name (including legacy IDs like claude-3-5-sonnet-…), enable this switch to use mapping.",
"modelMappingOnHint": "Claude Desktop only accepts the three role IDs claude-sonnet-* / claude-opus-* / claude-haiku-*. When enabled, CC Switch maps these three roles to your provider's actual models and keeps local routing running while in use.",
"routeMapTitle": "Model mapping",
"routeMapHint": "Choose a model role and CC Switch will generate a Claude Desktop-compatible route automatically. Use the menu display name for branded models such as DeepSeek or Kimi; requested model IDs are sent upstream exactly as entered.",
"routeMapHint": "Fill in the actual request model for each of the Sonnet, Opus, and Haiku tiers. Use the menu display name for branded models such as DeepSeek or Kimi. Any tier left blank inherits the Sonnet tier's model (or the first tier you filled in), so the Haiku that sub-agents call is always available.",
"routeModelLabel": "Model role",
"routeRoleSonnet": "Sonnet",
"routeRoleOpus": "Opus",
@@ -201,15 +201,15 @@
"supports1mShort": "1M",
"directModelListTitle": "Manually specify Claude Desktop models (advanced, optional)",
"directModelListCollapsedHint": "Native Claude model providers usually do not need this. Claude Desktop will fetch /v1/models automatically.",
"directModelListHint": "Only fill this when the provider's /v1/models is unavailable or does not return Claude Desktop-safe claude-* model IDs. Checking 1M declares 1M context support to Claude Desktop.",
"directModelInvalid": "Direct models must use claude-* / anthropic/claude-* model IDs",
"directModelListHint": "Only fill this when the provider's /v1/models is unavailable or does not return Claude Desktop-recognized Sonnet / Opus / Haiku model IDs. Checking 1M declares 1M context support to Claude Desktop.",
"directModelInvalid": "Direct models must use Claude Desktop-recognized Sonnet / Opus / Haiku model IDs",
"addModel": "Add model",
"addRoute": "Add model",
"routeInvalid": "Choose a model role and enter the requested model",
"routesRequired": "At least one model mapping is required",
"statusTitle": "Claude Desktop configuration needs attention",
"statusUnsupported": "This platform does not support writing Claude Desktop 3P configuration yet.",
"statusStaleRawModels": "The Claude Desktop profile contains non-claude-* model IDs. Newer Claude Desktop versions may reject it; switch to the current provider again to repair it.",
"statusStaleRawModels": "The Claude Desktop profile contains model IDs outside the Sonnet / Opus / Haiku role families. Newer Claude Desktop versions may reject it; switch to the current provider again to repair it.",
"statusMissingRouteMappings": "The current provider has model mapping enabled but no valid routes. Edit the provider and add at least one mapping.",
"statusGatewayTokenMissing": "The local routing token has not been generated yet. Switching to this provider again will write a new local token.",
"statusBaseUrlMismatch": "The Claude Desktop profile points to a different URL than the current provider. Current: {{actual}}; expected: {{expected}}. Switch to the current provider again to repair it.",
+6 -6
View File
@@ -187,10 +187,10 @@
"modeDirect": "直結",
"modeProxy": "ルーティング必要",
"modelMappingToggle": "モデルマッピングが必要",
"modelMappingOffHint": "プロバイダーがネイティブの Claude シリーズモデルを提供していない場合は、このスイッチを有効にしてください。",
"modelMappingOnHint": "Claude Desktop は現在モデル ID を制限しています。お使いのプロバイダーが Claude 系列以外のモデルを提供する場合、このスイッチを有効にし、使用中はローカルルーティングを起動したままにしてください。",
"modelMappingOffHint": "プロバイダーが Claude Desktop の三つの役割 IDclaude-sonnet-* / claude-opus-* / claude-haiku-*)を受け付ける場合のみ直結できます。それ以外のモデル名(claude-3-5-sonnet-… などの旧式 ID を含む)はこのスイッチを有効にしてマッピングを使ってください。",
"modelMappingOnHint": "Claude Desktop は claude-sonnet-* / claude-opus-* / claude-haiku-* の三つの役割 ID のみ受け付けます。有効にすると CC Switch がこの三役割をプロバイダーの実モデルにマッピングし、使用中はローカルルーティングを起動したままにします。",
"routeMapTitle": "モデルマッピング",
"routeMapHint": "モデル役割を選ぶと、CC Switch が Claude Desktop 互換ルートを自動生成します。DeepSeek や Kimi などのブランド名はメニュー表示名に入れ、実際のリクエストモデルは右側の入力どおりに送信されます。",
"routeMapHint": "Sonnet・Opus・Haiku の各区分に実際のリクエストモデルを入力してください。DeepSeek や Kimi などのブランド名はメニュー表示名に入れられます。空欄の区分は Sonnet(または最初に入力した区分)のモデルを引き継ぐため、サブ agent が呼び出す Haiku が常に利用可能になります。",
"routeModelLabel": "モデル役割",
"routeRoleSonnet": "Sonnet",
"routeRoleOpus": "Opus",
@@ -201,15 +201,15 @@
"supports1mShort": "1M",
"directModelListTitle": "Claude Desktop モデルを手動指定(高度・任意)",
"directModelListCollapsedHint": "ネイティブ Claude モデルのプロバイダーでは通常不要です。Claude Desktop が /v1/models を自動取得します。",
"directModelListHint": "プロバイダーの /v1/models が使えない、または Claude Desktop が認識できる claude-* モデル ID を返さない場合だけ入力してください。1M をオンにすると Claude Desktop に 1M コンテキスト対応を宣言します。",
"directModelInvalid": "直結モデルは claude-* / anthropic/claude-* のモデル ID を使う必要があります",
"directModelListHint": "プロバイダーの /v1/models が使えない、または Claude Desktop が認識できる Sonnet / Opus / Haiku モデル ID を返さない場合だけ入力してください。1M をオンにすると Claude Desktop に 1M コンテキスト対応を宣言します。",
"directModelInvalid": "直結モデルは Claude Desktop が認識できる Sonnet / Opus / Haiku モデル ID を使う必要があります",
"addModel": "モデルを追加",
"addRoute": "モデルを追加",
"routeInvalid": "モデル役割を選択し、リクエストモデルを入力してください",
"routesRequired": "少なくとも 1 つのモデルマッピングが必要です",
"statusTitle": "Claude Desktop 設定の確認が必要です",
"statusUnsupported": "このプラットフォームでは Claude Desktop 3P 設定の書き込みはまだサポートされていません。",
"statusStaleRawModels": "Claude Desktop profile に claude-* ではないモデル ID が含まれています。新しい Claude Desktop では拒否される可能性があります。現在のプロバイダーへ再度切り替えると修復できます。",
"statusStaleRawModels": "Claude Desktop profile に Sonnet / Opus / Haiku の役割ファミリー以外のモデル ID が含まれています。新しい Claude Desktop では拒否される可能性があります。現在のプロバイダーへ再度切り替えると修復できます。",
"statusMissingRouteMappings": "現在のプロバイダーはモデルマッピングを有効にしていますが、有効なルートがありません。プロバイダーを編集し、少なくとも 1 つのマッピングを追加してください。",
"statusGatewayTokenMissing": "ローカルルーティング token がまだ生成されていません。このプロバイダーへ再度切り替えると新しい token が書き込まれます。",
"statusBaseUrlMismatch": "Claude Desktop profile の URL が現在のプロバイダーと一致しません。現在: {{actual}}、期待値: {{expected}}。現在のプロバイダーへ再度切り替えると修復できます。",
+6 -6
View File
@@ -187,10 +187,10 @@
"modeDirect": "直連",
"modeProxy": "需要路由",
"modelMappingToggle": "需要模型對應",
"modelMappingOffHint": "如果您的供應商提供的不是原生 Claude 系列模型,請開啟此開關。",
"modelMappingOnHint": "Claude Desktop 目前對模型 ID 進行了限制,如果您的供應商提供的模型不是 Claude 系列模型,則需要開啟本開關,並在使用過程中保持本地路由開啟。",
"modelMappingOffHint": "僅當供應商直接接受 Claude Desktop 可識別的三檔角色 IDclaude-sonnet-* / claude-opus-* / claude-haiku-*)時才適用直連;其他模型名(含 claude-3-5-sonnet-… 等舊式 ID請開啟此開關走對應。",
"modelMappingOnHint": "Claude Desktop 只接受 claude-sonnet-* / claude-opus-* / claude-haiku-* 三檔角色 ID。開啟後 CC Switch 會把這三檔對應到供應商的實際模型,並在使用期間保持本地路由開啟。",
"routeMapTitle": "模型對應",
"routeMapHint": "選擇模型角色後,CC Switch 會自動產生 Claude Desktop 相容路由;選單顯示名稱可以填寫 DeepSeek、Kimi 等品牌模型,實際請求模型依右側填寫內容發送。",
"routeMapHint": "為 Sonnet、Opus、Haiku 三檔分別填寫實際請求模型;選單顯示名稱可寫 DeepSeek、Kimi 等品牌名。留空的檔會自動沿用 Sonnet(或第一個已填檔)的模型,確保子 agent 呼叫的 Haiku 始終可用。",
"routeModelLabel": "模型角色",
"routeRoleSonnet": "Sonnet",
"routeRoleOpus": "Opus",
@@ -201,15 +201,15 @@
"supports1mShort": "1M",
"directModelListTitle": "手動指定 Claude Desktop 模型清單(進階,選填)",
"directModelListCollapsedHint": "原生 Claude 模型供應商通常不需填寫,Claude Desktop 會自動讀取 /v1/models。",
"directModelListHint": "僅當供應商的 /v1/models 不可用或沒有回傳 Claude Desktop 可識別的 claude-* 模型名稱時填寫;勾選 1M 會向 Claude Desktop 聲明支援 1M 上下文。",
"directModelInvalid": "直連模型必須使用 claude-* / anthropic/claude-* 模型名稱",
"directModelListHint": "僅當供應商的 /v1/models 不可用或沒有回傳 Claude Desktop 可識別的 Sonnet / Opus / Haiku 模型名稱時填寫;勾選 1M 會向 Claude Desktop 聲明支援 1M 上下文。",
"directModelInvalid": "直連模型必須使用 Claude Desktop 可識別的 Sonnet / Opus / Haiku 模型名稱",
"addModel": "新增模型",
"addRoute": "新增模型",
"routeInvalid": "請選擇模型角色並填寫實際請求模型",
"routesRequired": "至少填寫一個模型對應",
"statusTitle": "Claude Desktop 設定需要檢查",
"statusUnsupported": "目前平台暫不支援 Claude Desktop 3P 設定寫入。",
"statusStaleRawModels": "Claude Desktop profile 中存在非 claude-* 模型名稱,新版 Claude Desktop 可能拒絕載入;重新切換目前供應商可修復。",
"statusStaleRawModels": "Claude Desktop profile 中存在非 Sonnet / Opus / Haiku 角色模型名稱,新版 Claude Desktop 可能拒絕載入;重新切換目前供應商可修復。",
"statusMissingRouteMappings": "目前供應商啟用了模型對應,但沒有有效路由;請編輯供應商並補全至少一個模型對應。",
"statusGatewayTokenMissing": "目前本地路由 token 尚未產生;重新切換該供應商會寫入新的本地 token。",
"statusBaseUrlMismatch": "Claude Desktop profile 指向的位址與目前供應商不一致;目前為 {{actual}},應為 {{expected}}。重新切換目前供應商可修復。",
+6 -6
View File
@@ -187,10 +187,10 @@
"modeDirect": "直连",
"modeProxy": "需要路由",
"modelMappingToggle": "需要模型映射",
"modelMappingOffHint": "如果您的供应商提供的不是原生 Claude 系列模型,请打开此开关。",
"modelMappingOnHint": "Claude Desktop 目前对模型 ID 进行了限制,如果您的供应商提供的模型不是 Claude 系列模型,则需要打开本开关,并在使用过程中保持本地路由开启。",
"modelMappingOffHint": "仅当供应商直接接受 Claude Desktop 可识别的三档角色 IDclaude-sonnet-* / claude-opus-* / claude-haiku-*)时才适用直连;其他模型名(含 claude-3-5-sonnet-… 等旧式 ID请打开此开关走映射。",
"modelMappingOnHint": "Claude Desktop 只接受 claude-sonnet-* / claude-opus-* / claude-haiku-* 三档角色 ID。开启后 CC Switch 会把这三档映射到供应商的实际模型,并在使用期间保持本地路由开启。",
"routeMapTitle": "模型映射",
"routeMapHint": "选择模型角色后,CC Switch 会自动生成 Claude Desktop 兼容路由;菜单显示名可写 DeepSeek、Kimi 等品牌模型,实际请求模型按右侧填写内容发送。",
"routeMapHint": "为 Sonnet、Opus、Haiku 三档分别填写实际请求模型;菜单显示名可写 DeepSeek、Kimi 等品牌名。留空的档会自动沿用 Sonnet(或第一个已填档)的模型,确保子 agent 调用的 Haiku 始终可用。",
"routeModelLabel": "模型角色",
"routeRoleSonnet": "Sonnet",
"routeRoleOpus": "Opus",
@@ -201,15 +201,15 @@
"supports1mShort": "1M",
"directModelListTitle": "手动指定 Claude Desktop 模型列表(高级,可选)",
"directModelListCollapsedHint": "原生 Claude 模型供应商通常不用填写,Claude Desktop 会自动读取 /v1/models。",
"directModelListHint": "仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 claude-* 模型名时填写;勾选 1M 会向 Claude Desktop 声明支持 1M 上下文。",
"directModelInvalid": "直连模型必须使用 claude-* / anthropic/claude-* 模型名",
"directModelListHint": "仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 Sonnet / Opus / Haiku 模型名时填写;勾选 1M 会向 Claude Desktop 声明支持 1M 上下文。",
"directModelInvalid": "直连模型必须使用 Claude Desktop 可识别的 Sonnet / Opus / Haiku 模型名",
"addModel": "添加模型",
"addRoute": "添加模型",
"routeInvalid": "请选择模型角色并填写实际请求模型",
"routesRequired": "至少填写一个模型映射",
"statusTitle": "Claude Desktop 配置需要检查",
"statusUnsupported": "当前平台暂不支持 Claude Desktop 3P 配置写入。",
"statusStaleRawModels": "Claude Desktop profile 中存在非 claude-* 模型名,新版 Claude Desktop 可能拒绝加载;重新切换当前供应商可修复。",
"statusStaleRawModels": "Claude Desktop profile 中存在非 Sonnet / Opus / Haiku 角色模型名,新版 Claude Desktop 可能拒绝加载;重新切换当前供应商可修复。",
"statusMissingRouteMappings": "当前供应商启用了模型映射,但没有有效路由;请编辑供应商并补全至少一个模型映射。",
"statusGatewayTokenMissing": "当前本地路由 token 尚未生成;重新切换该供应商会写入新的本地 token。",
"statusBaseUrlMismatch": "Claude Desktop profile 指向的地址与当前供应商不一致;当前为 {{actual}},应为 {{expected}}。重新切换当前供应商可修复。",