From f899c12cca0c89bd7ef62eca3d60708b65ae2acd Mon Sep 17 00:00:00 2001 From: Eunknight <403961282@qq.com> Date: Tue, 2 Jun 2026 04:01:26 +0800 Subject: [PATCH 1/2] fix(claude-desktop): stream check model fallback and disable official duplicate Two related fixes for Claude Desktop: 1. Stream health check returned 400 for third-party providers (e.g. MiMo) because the test defaulted to claude-haiku-4-5-20251001, which most third-party providers do not support. Fall back to the first upstream model stored in meta.claudeDesktopModelRoutes, matching the pattern already used for OpenCode/OpenClaw/Hermes. 2. Duplicating the official Claude Desktop provider triggered a backend validation error (missing ANTHROPIC_BASE_URL) because the official provider has no real settings. The duplicate was never useful, so disable the duplicate button for the official provider instead of copying it into a broken state. --- src-tauri/src/services/stream_check.rs | 22 +++++++++++++++++++- src/components/providers/ProviderActions.tsx | 7 +++++-- src/components/providers/ProviderCard.tsx | 10 +++++++-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src-tauri/src/services/stream_check.rs b/src-tauri/src/services/stream_check.rs index 665d8c0d27..a8cd684aa1 100644 --- a/src-tauri/src/services/stream_check.rs +++ b/src-tauri/src/services/stream_check.rs @@ -1393,10 +1393,15 @@ impl StreamCheckService { config: &StreamCheckConfig, ) -> String { match app_type { - AppType::Claude | AppType::ClaudeDesktop => { + AppType::Claude => { Self::extract_env_model(provider, "ANTHROPIC_MODEL") .unwrap_or_else(|| config.claude_model.clone()) } + AppType::ClaudeDesktop => { + Self::extract_env_model(provider, "ANTHROPIC_MODEL") + .or_else(|| Self::extract_desktop_model_from_routes(provider)) + .unwrap_or_else(|| config.claude_model.clone()) + } AppType::Codex => { Self::extract_codex_model(provider).unwrap_or_else(|| config.codex_model.clone()) } @@ -1450,6 +1455,21 @@ impl StreamCheckService { .filter(|value| !value.is_empty()) } + /// Extract the first upstream model name from Claude Desktop's model route mapping. + /// + /// Claude Desktop presets store model info in `meta.claudeDesktopModelRoutes` + /// rather than `settings_config.env.ANTHROPIC_MODEL`, so this extra fallback is needed. + fn extract_desktop_model_from_routes(provider: &Provider) -> Option { + provider + .meta + .as_ref()? + .claude_desktop_model_routes + .values() + .next() + .map(|route| route.model.trim().to_string()) + .filter(|m| !m.is_empty()) + } + fn extract_codex_model(provider: &Provider) -> Option { let config_text = provider .settings_config diff --git a/src/components/providers/ProviderActions.tsx b/src/components/providers/ProviderActions.tsx index 30dfe3d909..1cfe301f11 100644 --- a/src/components/providers/ProviderActions.tsx +++ b/src/components/providers/ProviderActions.tsx @@ -27,7 +27,7 @@ interface ProviderActionsProps { isOmo?: boolean; onSwitch: () => void; onEdit: () => void; - onDuplicate: () => void; + onDuplicate?: () => void; onTest?: () => void; onConfigureUsage?: () => void; onDelete: () => void; @@ -278,7 +278,10 @@ export function ProviderActions({ variant="ghost" onClick={onDuplicate} title={t("provider.duplicate")} - className={iconButtonClass} + className={cn( + iconButtonClass, + !onDuplicate && "opacity-40 cursor-not-allowed text-muted-foreground", + )} > diff --git a/src/components/providers/ProviderCard.tsx b/src/components/providers/ProviderCard.tsx index a61cead887..0d6e0c6022 100644 --- a/src/components/providers/ProviderCard.tsx +++ b/src/components/providers/ProviderCard.tsx @@ -48,7 +48,7 @@ interface ProviderCardProps { onDisableOmoSlim?: () => void; onConfigureUsage: (provider: Provider) => void; onOpenWebsite: (url: string) => void; - onDuplicate: (provider: Provider) => void; + onDuplicate?: (provider: Provider) => void; onTest?: (provider: Provider) => void; onOpenTerminal?: (provider: Provider) => void; isTesting?: boolean; @@ -529,7 +529,13 @@ export function ProviderCard({ isOmo={isAnyOmo} onSwitch={() => onSwitch(provider)} onEdit={() => onEdit(provider)} - onDuplicate={() => onDuplicate(provider)} + onDuplicate={ + appId === "claude-desktop" && isOfficial + ? undefined + : onDuplicate + ? () => onDuplicate(provider) + : undefined + } onTest={ onTest && !isOfficial && From a92a485ce1bab824be0ace6ed01ee2f1c32dcd77 Mon Sep 17 00:00:00 2001 From: Eunknight <403961282@qq.com> Date: Tue, 2 Jun 2026 04:38:08 +0800 Subject: [PATCH 2/2] style: fix rustfmt and prettier formatting --- src-tauri/src/services/stream_check.rs | 14 +++++--------- src/components/providers/ProviderActions.tsx | 3 ++- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/services/stream_check.rs b/src-tauri/src/services/stream_check.rs index a8cd684aa1..a15f93a915 100644 --- a/src-tauri/src/services/stream_check.rs +++ b/src-tauri/src/services/stream_check.rs @@ -1393,15 +1393,11 @@ impl StreamCheckService { config: &StreamCheckConfig, ) -> String { match app_type { - AppType::Claude => { - Self::extract_env_model(provider, "ANTHROPIC_MODEL") - .unwrap_or_else(|| config.claude_model.clone()) - } - AppType::ClaudeDesktop => { - Self::extract_env_model(provider, "ANTHROPIC_MODEL") - .or_else(|| Self::extract_desktop_model_from_routes(provider)) - .unwrap_or_else(|| config.claude_model.clone()) - } + AppType::Claude => Self::extract_env_model(provider, "ANTHROPIC_MODEL") + .unwrap_or_else(|| config.claude_model.clone()), + AppType::ClaudeDesktop => Self::extract_env_model(provider, "ANTHROPIC_MODEL") + .or_else(|| Self::extract_desktop_model_from_routes(provider)) + .unwrap_or_else(|| config.claude_model.clone()), AppType::Codex => { Self::extract_codex_model(provider).unwrap_or_else(|| config.codex_model.clone()) } diff --git a/src/components/providers/ProviderActions.tsx b/src/components/providers/ProviderActions.tsx index 1cfe301f11..1731c224d0 100644 --- a/src/components/providers/ProviderActions.tsx +++ b/src/components/providers/ProviderActions.tsx @@ -280,7 +280,8 @@ export function ProviderActions({ title={t("provider.duplicate")} className={cn( iconButtonClass, - !onDuplicate && "opacity-40 cursor-not-allowed text-muted-foreground", + !onDuplicate && + "opacity-40 cursor-not-allowed text-muted-foreground", )} >